diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7a6a8485e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,437 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +trim_trailing_whitespace = true +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_wrap_on_typing = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.scss] +indent_size = 2 +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[{*.ats,*.ts,*.tsx}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = true +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = true +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = false +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 1 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = true +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = true +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = true +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = false +ij_typescript_use_explicit_js_extension = global +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = false +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.cjs,*.js}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = true +ij_javascript_force_semicolon_style = true +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 1 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = true +ij_javascript_keep_simple_methods_in_one_line = true +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = true +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = true +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = true +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = true +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = true +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = false +ij_javascript_use_explicit_js_extension = global +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = false +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] +indent_size = 2 +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..1dd3435d6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: lint and build + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Cache node_modules + uses: actions/cache@v1.1.0 + with: + path: node_modules + key: node_modules + - name: Install dependencies + uses: borales/actions-yarn@v2.1.0 + with: + cmd: install + - name: build project + uses: borales/actions-yarn@v2.1.0 + with: + cmd: build diff --git a/package.json b/package.json index 40c0afd1f..7600458cb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,25 @@ "eject": "react-scripts eject" }, "eslintConfig": { - "extends": "react-app" + "parserOptions": { + "tsconfigRootDir": "", + "project": [ + "./tsconfig.json" + ] + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "react-app", + "standard", + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:import/recommended", + "plugin:import/typescript" + ] }, "browserslist": { "production": [ @@ -64,6 +82,16 @@ "devDependencies": { "@types/redux-devtools": "^3.0.47", "@types/redux-devtools-extension": "^2.13.2", + "@typescript-eslint/eslint-plugin": "^3.0.0", + "@typescript-eslint/parser": "^3.0.0", + "eslint-config-react-app": "^5.2.1", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-flowtype": "^5.1.0", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", "redux-devtools": "^3.5.0", "redux-devtools-extension": "^2.13.8" } diff --git a/src/api/config.ts b/src/api/config.ts index 9ba43ec03..7cb35c9d4 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -1,15 +1,15 @@ -import {FrontendConfigState} from "../redux/frontend-config/types"; -import {BackendConfigState} from "../redux/backend-config/types"; -import {expectResponseCode, getBackendUrl} from "../utils/apiUtils"; +import { FrontendConfigState } from '../redux/frontend-config/types' +import { BackendConfigState } from '../redux/backend-config/types' +import { expectResponseCode, getBackendUrl } from '../utils/apiUtils' -export const getBackendConfig = async () => { - const response = await fetch(getBackendUrl() + '/backend-config.json'); - expectResponseCode(response); - return await response.json() as Promise<BackendConfigState>; +export const getBackendConfig: () => Promise<BackendConfigState> = async () => { + const response = await fetch(getBackendUrl() + '/backend-config.json') + expectResponseCode(response) + return await response.json() as Promise<BackendConfigState> } -export const getFrontendConfig = async () => { - const response = await fetch('config.json'); - expectResponseCode(response) - return await response.json() as Promise<FrontendConfigState>; -} \ No newline at end of file +export const getFrontendConfig: () => Promise<FrontendConfigState> = async () => { + const response = await fetch('config.json') + expectResponseCode(response) + return await response.json() as Promise<FrontendConfigState> +} diff --git a/src/api/user.ts b/src/api/user.ts index d77536f78..c80ba106e 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,67 +1,72 @@ -import {expectResponseCode, getBackendUrl} from "../utils/apiUtils"; +import { expectResponseCode, getBackendUrl } from '../utils/apiUtils' -export const getMe = async () => { - return fetch('/me'); +export const getMe: (() => Promise<meResponse>) = async () => { + const response = await fetch('/me') + expectResponseCode(response) + return (await response.json()) as meResponse } -export const postEmailLogin = async (email: string, password: string) => { - const response = await fetch(getBackendUrl() + "/auth/email", { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({ - email: email, - password: password, - }) - }); - - expectResponseCode(response); - return await response.json(); +export interface meResponse { + id: string + name: string + photo: string } -export const postLdapLogin = async (username: string, password: string) => { - const response = await fetch(getBackendUrl() + "/auth/ldap", { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({ - username: username, - password: password, - }) +export const postEmailLogin: ((email: string, password: string) => Promise<void>) = async (email, password) => { + const response = await fetch(getBackendUrl() + '/auth/email', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: JSON.stringify({ + email: email, + password: password }) + }) - expectResponseCode(response) - return await response.json(); + expectResponseCode(response) } -export const postOpenIdLogin = async (openId: string) => { - const response = await fetch(getBackendUrl() + "/auth/openid", { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({ - openId: openId - }) +export const postLdapLogin: ((email: string, password: string) => Promise<void>) = async (username, password) => { + const response = await fetch(getBackendUrl() + '/auth/ldap', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: JSON.stringify({ + username: username, + password: password }) + }) - expectResponseCode(response) - return await response.json(); + expectResponseCode(response) +} + +export const postOpenIdLogin: ((openid: string) => Promise<void>) = async (openId: string) => { + const response = await fetch(getBackendUrl() + '/auth/openid', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: JSON.stringify({ + openId: openId + }) + }) + + expectResponseCode(response) } diff --git a/src/components/application-loader/application-loader.tsx b/src/components/application-loader/application-loader.tsx index 36eec0913..a70deef6f 100644 --- a/src/components/application-loader/application-loader.tsx +++ b/src/components/application-loader/application-loader.tsx @@ -1,35 +1,35 @@ -import React, {Fragment, useEffect, useState} from "react"; -import "./application-loader.scss"; -import {LoadingScreen} from "./loading-screen"; +import React, { Fragment, useEffect, useState } from 'react' +import './application-loader.scss' +import { LoadingScreen } from './loading-screen' interface ApplicationLoaderProps { - initTasks: Promise<any>[] + initTasks: Promise<void>[] } -export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({children, initTasks}) => { - const [failed, setFailed] = useState<boolean>(false); - const [doneTasks, setDoneTasks] = useState<number>(0); +export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({ children, initTasks }) => { + const [failed, setFailed] = useState<boolean>(false) + const [doneTasks, setDoneTasks] = useState<number>(0) - useEffect(() => { - setDoneTasks(0); - initTasks.forEach(task => { - (async () => { - try { - await task; - setDoneTasks(prevDoneTasks => { - return prevDoneTasks + 1; - }) - } catch (reason) { - setFailed(true); - console.error(reason); - } - })(); - }) - }, [initTasks]); + const runTask:((task: Promise<void>) => (Promise<void>)) = async (task) => { + await task + setDoneTasks(prevDoneTasks => { + return prevDoneTasks + 1 + }) + } - return ( - doneTasks < initTasks.length || initTasks.length === 0 ? - <LoadingScreen failed={failed}/> : - <Fragment>{children}</Fragment> - ); -} \ No newline at end of file + useEffect(() => { + setDoneTasks(0) + for (const task of initTasks) { + runTask(task).catch(reason => { + setFailed(true) + console.error(reason) + }) + } + }, [initTasks]) + + return ( + doneTasks < initTasks.length || initTasks.length === 0 + ? <LoadingScreen failed={failed}/> + : <Fragment>{children}</Fragment> + ) +} diff --git a/src/components/application-loader/loading-screen.tsx b/src/components/application-loader/loading-screen.tsx index 57d06f8b3..99689d42e 100644 --- a/src/components/application-loader/loading-screen.tsx +++ b/src/components/application-loader/loading-screen.tsx @@ -1,21 +1,21 @@ -import React from "react"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {Alert} from "react-bootstrap"; +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Alert } from 'react-bootstrap' export interface LoadingScreenProps { - failed: boolean + failed: boolean } -export const LoadingScreen: React.FC<LoadingScreenProps> = ({failed}) => { - return ( - <div className="loader middle"> - <div className="icon"> - <FontAwesomeIcon icon="file-alt" size="6x" - className={failed ? "animation-shake" : "animation-pulse"}/> - </div> - { - failed ? <Alert variant={"danger"}>An error occured while loading the application!</Alert> : null - } - </div> - ) -} \ No newline at end of file +export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failed }) => { + return ( + <div className="loader middle"> + <div className="icon"> + <FontAwesomeIcon icon="file-alt" size="6x" + className={failed ? 'animation-shake' : 'animation-pulse'}/> + </div> + { + failed ? <Alert variant={'danger'}>An error occurred while loading the application!</Alert> : null + } + </div> + ) +} diff --git a/src/components/element-separator/element-separator.tsx b/src/components/element-separator/element-separator.tsx index 14d065d75..266ab8cd2 100644 --- a/src/components/element-separator/element-separator.tsx +++ b/src/components/element-separator/element-separator.tsx @@ -1,27 +1,27 @@ -import React, {Fragment} from "react"; +import React, { Fragment } from 'react' export interface ElementSeparatorProps { separator: React.ReactElement } -export const ElementSeparator: React.FC<ElementSeparatorProps> = ({children, separator}) => { - return ( - <Fragment> - { - React.Children - .toArray(children) - .filter(child => child !== null) - .map((child, index) => { - return ( - <Fragment> - { - (index > 0 ) ? separator : null - } - {child} - </Fragment> - ) - }) - } - </Fragment> - ) -} \ No newline at end of file +export const ElementSeparator: React.FC<ElementSeparatorProps> = ({ children, separator }) => { + return ( + <Fragment> + { + React.Children + .toArray(children) + .filter(child => child !== null) + .map((child, index) => { + return ( + <Fragment> + { + (index > 0) ? separator : null + } + {child} + </Fragment> + ) + }) + } + </Fragment> + ) +} diff --git a/src/components/icon-button/icon-button.tsx b/src/components/icon-button/icon-button.tsx index fde6e01f8..9ab31d827 100644 --- a/src/components/icon-button/icon-button.tsx +++ b/src/components/icon-button/icon-button.tsx @@ -1,24 +1,24 @@ -import React from "react"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import "./icon-button.scss"; -import {IconProp} from "@fortawesome/fontawesome-svg-core"; -import {Button, ButtonProps} from "react-bootstrap"; +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import './icon-button.scss' +import { Button, ButtonProps } from 'react-bootstrap' +import { IconProp } from '../../utils/iconProp' export interface SocialButtonProps extends ButtonProps { - icon: IconProp - onClick?: () => void + icon: IconProp + onClick?: () => void } -export const IconButton: React.FC<SocialButtonProps> = ({icon, children, variant, onClick}) => { - return ( - <Button variant={variant} className={"btn-icon p-0 d-inline-flex align-items-stretch"} - onClick={() => onClick?.()}> - <span className="icon-part d-flex align-items-center"> - <FontAwesomeIcon icon={icon} className={"icon"}/> - </span> - <span className="text-part d-flex align-items-center"> - {children} - </span> - </Button> - ) +export const IconButton: React.FC<SocialButtonProps> = ({ icon, children, variant, onClick }) => { + return ( + <Button variant={variant} className={'btn-icon p-0 d-inline-flex align-items-stretch'} + onClick={() => onClick?.()}> + <span className="icon-part d-flex align-items-center"> + <FontAwesomeIcon icon={icon} className={'icon'}/> + </span> + <span className="text-part d-flex align-items-center"> + {children} + </span> + </Button> + ) } diff --git a/src/components/landing/layout/footer/external-link.tsx b/src/components/landing/layout/footer/external-link.tsx index 9c310b3dd..aefe62dc3 100644 --- a/src/components/landing/layout/footer/external-link.tsx +++ b/src/components/landing/layout/footer/external-link.tsx @@ -1,28 +1,27 @@ -import React, {Fragment} from "react"; -import {IconProp} from "@fortawesome/fontawesome-svg-core"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import React, { Fragment } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { IconProp } from '../../../../utils/iconProp' export interface ExternalLinkProp { - href: string; - text: string; - icon?: IconProp; + href: string; + text: string; + icon?: IconProp; } -export const ExternalLink: React.FC<ExternalLinkProp> = ({href, text, icon}) => { - return ( - <a href={href} - target="_blank" - rel="noopener noreferrer" - className="text-light"> - { - icon ? - <Fragment> - <FontAwesomeIcon icon={icon}/> - </Fragment> - : - null - } - {text} - </a> - ) +export const ExternalLink: React.FC<ExternalLinkProp> = ({ href, text, icon }) => { + return ( + <a href={href} + target="_blank" + rel="noopener noreferrer" + className="text-light"> + { + icon + ? <Fragment> + <FontAwesomeIcon icon={icon}/> + </Fragment> + : null + } + {text} + </a> + ) } diff --git a/src/components/landing/layout/footer/footer.tsx b/src/components/landing/layout/footer/footer.tsx index 864cb1a20..079155ec2 100644 --- a/src/components/landing/layout/footer/footer.tsx +++ b/src/components/landing/layout/footer/footer.tsx @@ -1,14 +1,14 @@ -import React from "react"; -import {LanguagePicker} from "./language-picker"; -import {PoweredByLinks} from "./powered-by-links"; -import {SocialLink} from "./social-links"; +import React from 'react' +import { LanguagePicker } from './language-picker' +import { PoweredByLinks } from './powered-by-links' +import { SocialLink } from './social-links' export const Footer: React.FC = () => { - return ( - <footer className="text-white-50 small"> - <LanguagePicker/> - <PoweredByLinks/> - <SocialLink/> - </footer> - ); + return ( + <footer className="text-white-50 small"> + <LanguagePicker/> + <PoweredByLinks/> + <SocialLink/> + </footer> + ) } diff --git a/src/components/landing/layout/footer/language-picker.tsx b/src/components/landing/layout/footer/language-picker.tsx index 74d69d4d7..6eaa6316f 100644 --- a/src/components/landing/layout/footer/language-picker.tsx +++ b/src/components/landing/layout/footer/language-picker.tsx @@ -1,54 +1,54 @@ -import React from "react"; -import {useTranslation} from "react-i18next"; -import moment from "moment"; -import { Form } from "react-bootstrap"; +import React from 'react' +import { useTranslation } from 'react-i18next' +import moment from 'moment' +import { Form } from 'react-bootstrap' const LanguagePicker: React.FC = () => { - const {i18n} = useTranslation(); + const { i18n } = useTranslation() - const onChangeLang = (event: React.ChangeEvent<HTMLSelectElement>) => { - moment.locale(event.currentTarget.value); - i18n.changeLanguage(event.currentTarget.value); - } + const onChangeLang = async (event: React.ChangeEvent<HTMLSelectElement>) => { + moment.locale(event.currentTarget.value) + await i18n.changeLanguage(event.currentTarget.value) + } - return ( - <Form.Control - as="select" - size="sm" - className="mb-2 mx-auto w-auto" - value={i18n.language} - onChange={onChangeLang} - > - <option value="en">English</option> - <option value="zh-CN">简体中文</option> - <option value="zh-TW">繁體中文</option> - <option value="fr">Français</option> - <option value="de">Deutsch</option> - <option value="ja">日本語</option> - <option value="es">Español</option> - <option value="ca">Català</option> - <option value="el">Ελληνικά</option> - <option value="pt">Português</option> - <option value="it">Italiano</option> - <option value="tr">Türkçe</option> - <option value="ru">Русский</option> - <option value="nl">Nederlands</option> - <option value="hr">Hrvatski</option> - <option value="pl">Polski</option> - <option value="uk">Українська</option> - <option value="hi">हिन्दी</option> - <option value="sv">Svenska</option> - <option value="eo">Esperanto</option> - <option value="da">Dansk</option> - <option value="ko">한국어</option> - <option value="id">Bahasa Indonesia</option> - <option value="sr">Cрпски</option> - <option value="vi">Tiếng Việt</option> - <option value="ar">العربية</option> - <option value="cs">Česky</option> - <option value="sk">Slovensky</option> - </Form.Control> - ) + return ( + <Form.Control + as="select" + size="sm" + className="mb-2 mx-auto w-auto" + value={i18n.language} + onChange={onChangeLang} + > + <option value="en">English</option> + <option value="zh-CN">简体中文</option> + <option value="zh-TW">繁體中文</option> + <option value="fr">Français</option> + <option value="de">Deutsch</option> + <option value="ja">日本語</option> + <option value="es">Español</option> + <option value="ca">Català</option> + <option value="el">Ελληνικά</option> + <option value="pt">Português</option> + <option value="it">Italiano</option> + <option value="tr">Türkçe</option> + <option value="ru">Русский</option> + <option value="nl">Nederlands</option> + <option value="hr">Hrvatski</option> + <option value="pl">Polski</option> + <option value="uk">Українська</option> + <option value="hi">हिन्दी</option> + <option value="sv">Svenska</option> + <option value="eo">Esperanto</option> + <option value="da">Dansk</option> + <option value="ko">한국어</option> + <option value="id">Bahasa Indonesia</option> + <option value="sr">Cрпски</option> + <option value="vi">Tiếng Việt</option> + <option value="ar">العربية</option> + <option value="cs">Česky</option> + <option value="sk">Slovensky</option> + </Form.Control> + ) } export { LanguagePicker } diff --git a/src/components/landing/layout/footer/powered-by-links.tsx b/src/components/landing/layout/footer/powered-by-links.tsx index 07a48c9ad..05a414a4d 100644 --- a/src/components/landing/layout/footer/powered-by-links.tsx +++ b/src/components/landing/layout/footer/powered-by-links.tsx @@ -1,51 +1,35 @@ -import {Trans, useTranslation} from "react-i18next"; -import {TranslatedLink} from "./translated-link"; -import React, {Fragment} from "react"; -import {ExternalLink} from "./external-link"; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../redux"; +import { Trans, useTranslation } from 'react-i18next' +import { TranslatedLink } from './translated-link' +import React, { Fragment } from 'react' +import { ExternalLink } from './external-link' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../redux' -const PoweredByLinks: React.FC = () => { - useTranslation(); - const defaultLinks = [ - { - href: '/s/release-notes', - i18nKey: 'releases' - }, - { - href: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d', - i18nKey: 'sourceCode' - } - ] +export const PoweredByLinks: React.FC = () => { + useTranslation() - const config = useSelector((state: ApplicationState) => state.backendConfig); + const defaultLinks = + { + releases: '/s/release-notes', + sourceCode: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d' + } - const specialLinks = Object.entries(config.specialLinks) - .filter(([_, value]) => value !== "") - .map(([key, value]) => { - return { - href: value, - i18nKey: key - } - }) + const config = useSelector((state: ApplicationState) => state.backendConfig) - return ( - <p> - <Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/> - - { - (defaultLinks.concat(specialLinks)).map(({href, i18nKey}) => - <Fragment key={i18nKey}> - | - <TranslatedLink - href={href} - i18nKey={i18nKey} - /> - </Fragment> - ) - } - </p> - ) + return ( + <p> + <Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/> + { + Object.entries({ ...defaultLinks, ...(config.specialLinks) }).map(([i18nKey, href]) => + <Fragment key={i18nKey}> + | + <TranslatedLink + href={href} + i18nKey={i18nKey} + /> + </Fragment> + ) + } + </p> + ) } - -export { PoweredByLinks } diff --git a/src/components/landing/layout/footer/social-links.tsx b/src/components/landing/layout/footer/social-links.tsx index 5765a21d2..e7a4007d7 100644 --- a/src/components/landing/layout/footer/social-links.tsx +++ b/src/components/landing/layout/footer/social-links.tsx @@ -1,20 +1,20 @@ -import React from "react"; -import {Trans, useTranslation} from "react-i18next"; -import {ExternalLink} from "./external-link"; +import React from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { ExternalLink } from './external-link' const SocialLink: React.FC = () => { - useTranslation(); - return ( - <p> - <Trans i18nKey="followUs" components={[ - <ExternalLink href="https://github.com/codimd/server" icon={['fab', 'github']} text="GitHub"/>, - <ExternalLink href="https://community.codimd.org" icon={['fab', 'discourse']} text="Discourse"/>, - <ExternalLink href="https://riot.im/app/#/room/#codimd:matrix.org" icon="comment" text="Riot"/>, - <ExternalLink href="https://social.codimd.org/mastodon" icon={['fab', 'mastodon']} text="Mastodon"/>, - <ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/> - ]}/> - </p> - ) + useTranslation() + return ( + <p> + <Trans i18nKey="followUs" components={[ + <ExternalLink href="https://github.com/codimd/server" icon={['fab', 'github']} text="GitHub"/>, + <ExternalLink href="https://community.codimd.org" icon={['fab', 'discourse']} text="Discourse"/>, + <ExternalLink href="https://riot.im/app/#/room/#codimd:matrix.org" icon="comment" text="Riot"/>, + <ExternalLink href="https://social.codimd.org/mastodon" icon={['fab', 'mastodon']} text="Mastodon"/>, + <ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/> + ]}/> + </p> + ) } export { SocialLink } diff --git a/src/components/landing/layout/footer/translated-link.tsx b/src/components/landing/layout/footer/translated-link.tsx index b4c703ab8..288b26ce1 100644 --- a/src/components/landing/layout/footer/translated-link.tsx +++ b/src/components/landing/layout/footer/translated-link.tsx @@ -1,21 +1,21 @@ -import React from "react"; -import {Trans, useTranslation} from "react-i18next"; +import React from 'react' +import { Trans, useTranslation } from 'react-i18next' export interface TranslatedLinkProps { - href: string; - i18nKey: string; + href: string; + i18nKey: string; } -const TranslatedLink: React.FC<TranslatedLinkProps> = ({href, i18nKey}) => { - useTranslation(); - return ( - <a href={href} - target="_blank" - rel="noopener noreferrer" - className="text-light"> - <Trans i18nKey={i18nKey}/> - </a> - ) +const TranslatedLink: React.FC<TranslatedLinkProps> = ({ href, i18nKey }) => { + useTranslation() + return ( + <a href={href} + target="_blank" + rel="noopener noreferrer" + className="text-light"> + <Trans i18nKey={i18nKey}/> + </a> + ) } -export {TranslatedLink} +export { TranslatedLink } diff --git a/src/components/landing/layout/index.tsx b/src/components/landing/layout/index.tsx index fb0eebb51..59337d057 100644 --- a/src/components/landing/layout/index.tsx +++ b/src/components/landing/layout/index.tsx @@ -1,30 +1,30 @@ -import React from "react"; -import {Redirect, Route, Switch} from "react-router-dom"; -import {History} from "../pages/history/history"; -import {Intro} from "../pages/intro/intro"; -import {Container} from "react-bootstrap"; -import {HeaderBar} from "./navigation/header-bar/header-bar"; -import {Footer} from "./footer/footer"; -import "./style/index.scss"; -import {Login} from "../pages/login/login"; +import React from 'react' +import { Redirect, Route, Switch } from 'react-router-dom' +import { History } from '../pages/history/history' +import { Intro } from '../pages/intro/intro' +import { Container } from 'react-bootstrap' +import { HeaderBar } from './navigation/header-bar/header-bar' +import { Footer } from './footer/footer' +import './style/index.scss' +import { Login } from '../pages/login/login' export const Landing: React.FC = () => { - return (<Container> - <HeaderBar/> - <Switch> - <Route path="/history"> - <History/> - </Route> - <Route path="/intro"> - <Intro/> - </Route> - <Route path="/login"> - <Login/> - </Route> - <Route path="/"> - <Redirect to="/intro"/> - </Route> - </Switch> - <Footer/> - </Container>) + return (<Container> + <HeaderBar/> + <Switch> + <Route path="/history"> + <History/> + </Route> + <Route path="/intro"> + <Intro/> + </Route> + <Route path="/login"> + <Login/> + </Route> + <Route path="/"> + <Redirect to="/intro"/> + </Route> + </Switch> + <Footer/> + </Container>) } diff --git a/src/components/landing/layout/navigation/header-bar/header-bar.tsx b/src/components/landing/layout/navigation/header-bar/header-bar.tsx index e3ae70a4e..6b3a562ce 100644 --- a/src/components/landing/layout/navigation/header-bar/header-bar.tsx +++ b/src/components/landing/layout/navigation/header-bar/header-bar.tsx @@ -1,50 +1,48 @@ -import React, {Fragment} from 'react' -import {Navbar} from 'react-bootstrap'; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../../redux"; -import {NewUserNoteButton} from "../new-user-note-button"; -import {UserDropdown} from "../user-dropdown/user-dropdown"; -import {SignInButton} from "../sign-in-button"; -import {NewGuestNoteButton} from "../new-guest-note-button"; -import {LoginStatus} from "../../../../../redux/user/types"; -import {HeaderNavLink} from "../header-nav-link"; -import "./header-bar.scss"; -import {Trans, useTranslation} from "react-i18next"; +import React, { Fragment } from 'react' +import { Navbar } from 'react-bootstrap' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../../redux' +import { NewUserNoteButton } from '../new-user-note-button' +import { UserDropdown } from '../user-dropdown/user-dropdown' +import { SignInButton } from '../sign-in-button' +import { NewGuestNoteButton } from '../new-guest-note-button' +import { LoginStatus } from '../../../../../redux/user/types' +import { HeaderNavLink } from '../header-nav-link' +import './header-bar.scss' +import { Trans, useTranslation } from 'react-i18next' const HeaderBar: React.FC = () => { - useTranslation() - const user = useSelector((state: ApplicationState) => state.user); + useTranslation() + const user = useSelector((state: ApplicationState) => state.user) - return ( - <Navbar className="justify-content-between"> - <div className="nav header-nav"> - <HeaderNavLink to="/intro"> - <Trans i18nKey="intro"/> - </HeaderNavLink> - <HeaderNavLink to="/history"> - <Trans i18nKey="history"/> - </HeaderNavLink> - </div> - <div className="d-inline-flex"> - {user.status === LoginStatus.forbidden ? - <Fragment> - <span className={"mr-1"}> - <NewGuestNoteButton/> - </span> - <SignInButton/> - </Fragment> - : - <Fragment> - <span className={"mr-1"}> - <NewUserNoteButton/> - </span> - <UserDropdown/> - </Fragment> - } - </div> - </Navbar> - ); + return ( + <Navbar className="justify-content-between"> + <div className="nav header-nav"> + <HeaderNavLink to="/intro"> + <Trans i18nKey="intro"/> + </HeaderNavLink> + <HeaderNavLink to="/history"> + <Trans i18nKey="history"/> + </HeaderNavLink> + </div> + <div className="d-inline-flex"> + {user.status === LoginStatus.forbidden + ? <Fragment> + <span className={'mr-1'}> + <NewGuestNoteButton/> + </span> + <SignInButton/> + </Fragment> + : <Fragment> + <span className={'mr-1'}> + <NewUserNoteButton/> + </span> + <UserDropdown/> + </Fragment> + } + </div> + </Navbar> + ) } -export {HeaderBar} - +export { HeaderBar } diff --git a/src/components/landing/layout/navigation/header-nav-link.tsx b/src/components/landing/layout/navigation/header-nav-link.tsx index 2cedeb4c9..2c4a0918a 100644 --- a/src/components/landing/layout/navigation/header-nav-link.tsx +++ b/src/components/landing/layout/navigation/header-nav-link.tsx @@ -1,17 +1,17 @@ -import {Nav} from "react-bootstrap"; -import {LinkContainer} from "react-router-bootstrap"; -import React from "react"; +import { Nav } from 'react-bootstrap' +import { LinkContainer } from 'react-router-bootstrap' +import React from 'react' export interface HeaderNavLinkProps { - to: string + to: string } export const HeaderNavLink: React.FC<HeaderNavLinkProps> = (props) => { - return ( - <Nav.Item> - <LinkContainer to={props.to}> - <Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link> - </LinkContainer> - </Nav.Item> - ); -} \ No newline at end of file + return ( + <Nav.Item> + <LinkContainer to={props.to}> + <Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link> + </LinkContainer> + </Nav.Item> + ) +} diff --git a/src/components/landing/layout/navigation/new-guest-note-button.tsx b/src/components/landing/layout/navigation/new-guest-note-button.tsx index 24abb203f..85e23535f 100644 --- a/src/components/landing/layout/navigation/new-guest-note-button.tsx +++ b/src/components/landing/layout/navigation/new-guest-note-button.tsx @@ -1,21 +1,21 @@ -import React from "react"; -import {LinkContainer} from "react-router-bootstrap"; -import {Button} from "react-bootstrap"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {Trans, useTranslation} from "react-i18next"; +import React from 'react' +import { LinkContainer } from 'react-router-bootstrap' +import { Button } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Trans, useTranslation } from 'react-i18next' export const NewGuestNoteButton: React.FC = () => { - const {i18n} = useTranslation(); - return ( - <LinkContainer to={'/new'} title={i18n.t("newGuestNote")}> - <Button - variant="primary" - size="sm" - className="d-inline-flex align-items-center"> - <FontAwesomeIcon icon="plus" className="mr-1"/> - <span> - <Trans i18nKey='newGuestNote'/> - </span> - </Button> - </LinkContainer>) + const { i18n } = useTranslation() + return ( + <LinkContainer to={'/new'} title={i18n.t('newGuestNote')}> + <Button + variant="primary" + size="sm" + className="d-inline-flex align-items-center"> + <FontAwesomeIcon icon="plus" className="mr-1"/> + <span> + <Trans i18nKey='newGuestNote'/> + </span> + </Button> + </LinkContainer>) } diff --git a/src/components/landing/layout/navigation/new-user-note-button.tsx b/src/components/landing/layout/navigation/new-user-note-button.tsx index aa6e8f57c..70082e967 100644 --- a/src/components/landing/layout/navigation/new-user-note-button.tsx +++ b/src/components/landing/layout/navigation/new-user-note-button.tsx @@ -1,22 +1,22 @@ -import {LinkContainer} from "react-router-bootstrap"; -import {Button} from "react-bootstrap"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import React from "react"; -import {Trans, useTranslation} from "react-i18next"; +import { LinkContainer } from 'react-router-bootstrap' +import { Button } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import { Trans, useTranslation } from 'react-i18next' export const NewUserNoteButton: React.FC = () => { - const {i18n} = useTranslation() - return ( - <LinkContainer to={'/new'} title={i18n.t("newNote")}> - <Button - variant="primary" - size="sm" - className="d-inline-flex align-items-center"> - <FontAwesomeIcon icon="plus" className="mr-1"/> - <span> - <Trans i18nKey='newNote'/> - </span> - </Button> - </LinkContainer> - ) + const { i18n } = useTranslation() + return ( + <LinkContainer to={'/new'} title={i18n.t('newNote')}> + <Button + variant="primary" + size="sm" + className="d-inline-flex align-items-center"> + <FontAwesomeIcon icon="plus" className="mr-1"/> + <span> + <Trans i18nKey='newNote'/> + </span> + </Button> + </LinkContainer> + ) } diff --git a/src/components/landing/layout/navigation/sign-in-button.tsx b/src/components/landing/layout/navigation/sign-in-button.tsx index 89ccfa64d..e148c61df 100644 --- a/src/components/landing/layout/navigation/sign-in-button.tsx +++ b/src/components/landing/layout/navigation/sign-in-button.tsx @@ -1,19 +1,19 @@ -import React from "react"; -import {Button} from "react-bootstrap"; -import {Trans, useTranslation} from "react-i18next"; -import {LinkContainer} from "react-router-bootstrap"; +import React from 'react' +import { Button } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { LinkContainer } from 'react-router-bootstrap' export const SignInButton: React.FC = () => { - const {i18n} = useTranslation(); + const { i18n } = useTranslation() - return ( - <LinkContainer to="/login" title={i18n.t("signIn")}> - <Button - variant="success" - size="sm" - > - <Trans i18nKey="signIn"/> - </Button> - </LinkContainer> - ) + return ( + <LinkContainer to="/login" title={i18n.t('signIn')}> + <Button + variant="success" + size="sm" + > + <Trans i18nKey="signIn"/> + </Button> + </LinkContainer> + ) } diff --git a/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx b/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx index 3d26c779c..2333409e5 100644 --- a/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx +++ b/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx @@ -1,52 +1,52 @@ -import {Dropdown} from "react-bootstrap"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import React from "react"; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../../redux"; -import {LinkContainer} from "react-router-bootstrap"; -import {clearUser} from "../../../../../redux/user/methods"; -import "./user-dropdown.scss"; -import {Trans} from "react-i18next"; +import { Dropdown } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../../redux' +import { LinkContainer } from 'react-router-bootstrap' +import { clearUser } from '../../../../../redux/user/methods' +import './user-dropdown.scss' +import { Trans } from 'react-i18next' export const UserDropdown: React.FC = () => { - const user = useSelector((state: ApplicationState) => state.user); + const user = useSelector((state: ApplicationState) => state.user) - return ( - <Dropdown alignRight> - <Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic"> - <div className='d-inline-flex align-items-baseline'> - <img - src={user.photo} - className="user-avatar" - alt={`Avatar of ${user.name}`} - /><span>{user.name}</span> - </div> - </Dropdown.Toggle> + return ( + <Dropdown alignRight> + <Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic"> + <div className='d-inline-flex align-items-baseline'> + <img + src={user.photo} + className="user-avatar" + alt={`Avatar of ${user.name}`} + /><span>{user.name}</span> + </div> + </Dropdown.Toggle> - <Dropdown.Menu> - <LinkContainer to={`/features`}> - <Dropdown.Item> - <FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/> - <Trans i18nKey="features"/> - </Dropdown.Item> - </LinkContainer> - <LinkContainer to={`/me/export`}> - <Dropdown.Item> - <FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/> - <Trans i18nKey="exportUserData"/> - </Dropdown.Item> - </LinkContainer> - <Dropdown.Item href="#"> - <FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/> - <Trans i18nKey="deleteUser"/> - </Dropdown.Item> - <Dropdown.Item - onClick={() => { - clearUser(); - }}> - <FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/> - <Trans i18nKey="signOut"/> - </Dropdown.Item> - </Dropdown.Menu> - </Dropdown>) -}; + <Dropdown.Menu> + <LinkContainer to={'/features'}> + <Dropdown.Item> + <FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/> + <Trans i18nKey="features"/> + </Dropdown.Item> + </LinkContainer> + <LinkContainer to={'/me/export'}> + <Dropdown.Item> + <FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/> + <Trans i18nKey="exportUserData"/> + </Dropdown.Item> + </LinkContainer> + <Dropdown.Item href="#"> + <FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/> + <Trans i18nKey="deleteUser"/> + </Dropdown.Item> + <Dropdown.Item + onClick={() => { + clearUser() + }}> + <FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/> + <Trans i18nKey="signOut"/> + </Dropdown.Item> + </Dropdown.Menu> + </Dropdown>) +} diff --git a/src/components/landing/layout/style/font-pack.scss b/src/components/landing/layout/style/font-pack.scss index 4804d1918..bd0baddc9 100644 --- a/src/components/landing/layout/style/font-pack.scss +++ b/src/components/landing/layout/style/font-pack.scss @@ -67,7 +67,7 @@ font-family: 'Source Sans Pro'; font-style: normal; font-weight: 300; - src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('/src/components/indexnents/index/fonts/SourceSansPro-Light.woff') format('woff'); + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('fonts/SourceSansPro-Light.woff') format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* vietnamese */ diff --git a/src/components/landing/pages/history/common/close-button.tsx b/src/components/landing/pages/history/common/close-button.tsx index d7643494c..5625b8d57 100644 --- a/src/components/landing/pages/history/common/close-button.tsx +++ b/src/components/landing/pages/history/common/close-button.tsx @@ -1,21 +1,21 @@ -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import React from "react"; -import "./close-button.scss" -import {Button} from "react-bootstrap"; +import React from 'react' +import { Button } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import './close-button.scss' export interface CloseButtonProps { - isDark: boolean; + isDark: boolean; } -const CloseButton: React.FC<CloseButtonProps> = ({isDark}) => { - return ( - <Button variant={isDark ? "secondary" : "light"}> - <FontAwesomeIcon - className="history-close" - icon="times" - /> - </Button> - ); +const CloseButton: React.FC<CloseButtonProps> = ({ isDark }) => { + return ( + <Button variant={isDark ? 'secondary' : 'light'}> + <FontAwesomeIcon + className="history-close" + icon="times" + /> + </Button> + ) } -export {CloseButton} +export { CloseButton } diff --git a/src/components/landing/pages/history/common/pin-button.tsx b/src/components/landing/pages/history/common/pin-button.tsx index 6f1d036dd..e4f731253 100644 --- a/src/components/landing/pages/history/common/pin-button.tsx +++ b/src/components/landing/pages/history/common/pin-button.tsx @@ -1,22 +1,22 @@ -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import React from "react"; -import "./pin-button.scss" -import {Button} from "react-bootstrap"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import './pin-button.scss' +import { Button } from 'react-bootstrap' export interface PinButtonProps { - isPinned: boolean; - onPinClick: () => void; - isDark: boolean; + isPinned: boolean; + onPinClick: () => void; + isDark: boolean; } -export const PinButton: React.FC<PinButtonProps> = ({isPinned, onPinClick, isDark}) => { - return ( - <Button variant={isDark ? "secondary" : "light"} - onClick={onPinClick}> - <FontAwesomeIcon - icon="thumbtack" - className={`history-pin ${isPinned ? 'active' : ''}`} - /> - </Button> - ); +export const PinButton: React.FC<PinButtonProps> = ({ isPinned, onPinClick, isDark }) => { + return ( + <Button variant={isDark ? 'secondary' : 'light'} + onClick={onPinClick}> + <FontAwesomeIcon + icon="thumbtack" + className={`history-pin ${isPinned ? 'active' : ''}`} + /> + </Button> + ) } diff --git a/src/components/landing/pages/history/history-card/history-card-list.tsx b/src/components/landing/pages/history/history-card/history-card-list.tsx index 03ca11c58..a29a69838 100644 --- a/src/components/landing/pages/history/history-card/history-card-list.tsx +++ b/src/components/landing/pages/history/history-card/history-card-list.tsx @@ -1,23 +1,22 @@ import React from 'react' -import {HistoryEntriesProps} from "../history-content/history-content"; -import {HistoryCard} from "./history-card"; -import {Pager} from '../../../../pagination/pager'; -import {Row} from 'react-bootstrap'; +import { HistoryEntriesProps } from '../history-content/history-content' +import { HistoryCard } from './history-card' +import { Pager } from '../../../../pagination/pager' +import { Row } from 'react-bootstrap' -export const HistoryCardList: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => { - - return ( - <Row className="justify-content-center"> - <Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}> - { - entries.map((entry) => ( - <HistoryCard - key={entry.id} - entry={entry} - onPinClick={onPinClick} - />)) - } - </Pager> - </Row> - ); +export const HistoryCardList: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => { + return ( + <Row className="justify-content-center"> + <Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}> + { + entries.map((entry) => ( + <HistoryCard + key={entry.id} + entry={entry} + onPinClick={onPinClick} + />)) + } + </Pager> + </Row> + ) } diff --git a/src/components/landing/pages/history/history-card/history-card.tsx b/src/components/landing/pages/history/history-card/history-card.tsx index 19d4f5342..3c092287c 100644 --- a/src/components/landing/pages/history/history-card/history-card.tsx +++ b/src/components/landing/pages/history/history-card/history-card.tsx @@ -1,38 +1,38 @@ import React from 'react' -import {Badge, Card} from 'react-bootstrap' -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' -import {PinButton} from "../common/pin-button"; -import {CloseButton} from "../common/close-button"; -import moment from "moment"; -import {useTranslation} from "react-i18next"; -import {HistoryEntryProps} from "../history-content/history-content"; -import {formatHistoryDate} from "../../../../../utils/historyUtils"; +import { Badge, Card } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { PinButton } from '../common/pin-button' +import { CloseButton } from '../common/close-button' +import moment from 'moment' +import { useTranslation } from 'react-i18next' +import { HistoryEntryProps } from '../history-content/history-content' +import { formatHistoryDate } from '../../../../../utils/historyUtils' -export const HistoryCard: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => { - useTranslation() - return ( - <div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4"> - <Card className="p-0" text={"dark"} bg={"light"}> - <div className="d-flex justify-content-between p-2 align-items-start"> - <PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => { - onPinClick(entry.id) - }}/> - <Card.Title className="m-0 mt-3">{entry.title}</Card.Title> - <CloseButton isDark={false}/> - </div> - <Card.Body> - <div className="text-black-50"> - <FontAwesomeIcon icon="clock"/> {moment(entry.lastVisited).fromNow()}<br/> - {formatHistoryDate(entry.lastVisited)} - <div> - { - entry.tags.map((tag) => <Badge variant={"dark"} className={"mr-1 mb-1"} - key={tag}>{tag}</Badge>) - } - </div> - </div> - </Card.Body> - </Card> +export const HistoryCard: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => { + useTranslation() + return ( + <div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4"> + <Card className="p-0" text={'dark'} bg={'light'}> + <div className="d-flex justify-content-between p-2 align-items-start"> + <PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => { + onPinClick(entry.id) + }}/> + <Card.Title className="m-0 mt-3">{entry.title}</Card.Title> + <CloseButton isDark={false}/> </div> - ) + <Card.Body> + <div className="text-black-50"> + <FontAwesomeIcon icon="clock"/> {moment(entry.lastVisited).fromNow()}<br/> + {formatHistoryDate(entry.lastVisited)} + <div> + { + entry.tags.map((tag) => <Badge variant={'dark'} className={'mr-1 mb-1'} + key={tag}>{tag}</Badge>) + } + </div> + </div> + </Card.Body> + </Card> + </div> + ) } diff --git a/src/components/landing/pages/history/history-content/history-content.tsx b/src/components/landing/pages/history/history-content/history-content.tsx index 24bbf4617..21d735899 100644 --- a/src/components/landing/pages/history/history-content/history-content.tsx +++ b/src/components/landing/pages/history/history-content/history-content.tsx @@ -1,21 +1,21 @@ -import React, {Fragment, useState} from "react"; -import {HistoryEntry, pinClick} from "../history"; -import {HistoryTable} from "../history-table/history-table"; -import {Alert, Row} from "react-bootstrap"; -import {Trans} from "react-i18next"; -import {HistoryCardList} from "../history-card/history-card-list"; -import {ViewStateEnum} from "../history-toolbar/history-toolbar"; -import {PagerPagination} from "../../../../pagination/pager-pagination"; +import React, { Fragment, useState } from 'react' +import { HistoryEntry, pinClick } from '../history' +import { HistoryTable } from '../history-table/history-table' +import { Alert, Row } from 'react-bootstrap' +import { Trans } from 'react-i18next' +import { HistoryCardList } from '../history-card/history-card-list' +import { ViewStateEnum } from '../history-toolbar/history-toolbar' +import { PagerPagination } from '../../../../pagination/pager-pagination' export interface HistoryContentProps { - viewState: ViewStateEnum - entries: HistoryEntry[] - onPinClick: pinClick + viewState: ViewStateEnum + entries: HistoryEntry[] + onPinClick: pinClick } export interface HistoryEntryProps { - entry: HistoryEntry, - onPinClick: pinClick + entry: HistoryEntry, + onPinClick: pinClick } export interface HistoryEntriesProps { @@ -25,39 +25,38 @@ export interface HistoryEntriesProps { onLastPageIndexChange: (lastPageIndex: number) => void } +export const HistoryContent: React.FC<HistoryContentProps> = ({ viewState, entries, onPinClick }) => { + const [pageIndex, setPageIndex] = useState(0) + const [lastPageIndex, setLastPageIndex] = useState(0) -export const HistoryContent: React.FC<HistoryContentProps> = ({viewState, entries, onPinClick}) => { - const [pageIndex, setPageIndex] = useState(0); - const [lastPageIndex, setLastPageIndex] = useState(0); - - if (entries.length === 0) { - return ( - <Row className={"justify-content-center"}> - <Alert variant={"secondary"}> - <Trans i18nKey={"noHistory"}/> - </Alert> - </Row> - ); - } - - const mapViewStateToComponent = (viewState: ViewStateEnum) => { - switch (viewState) { - default: - case ViewStateEnum.card: - return <HistoryCardList entries={entries} onPinClick={onPinClick} pageIndex={pageIndex} - onLastPageIndexChange={setLastPageIndex}/> - case ViewStateEnum.table: - return <HistoryTable entries={entries} onPinClick={onPinClick} pageIndex={pageIndex} - onLastPageIndexChange={setLastPageIndex}/> - } - } - + if (entries.length === 0) { return ( - <Fragment> - {mapViewStateToComponent(viewState)} - <Row className="justify-content-center"> - <PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex} - onPageChange={setPageIndex}/> - </Row> - </Fragment>); -} \ No newline at end of file + <Row className={'justify-content-center'}> + <Alert variant={'secondary'}> + <Trans i18nKey={'noHistory'}/> + </Alert> + </Row> + ) + } + + const mapViewStateToComponent = (viewState: ViewStateEnum) => { + switch (viewState) { + default: + case ViewStateEnum.card: + return <HistoryCardList entries={entries} onPinClick={onPinClick} pageIndex={pageIndex} + onLastPageIndexChange={setLastPageIndex}/> + case ViewStateEnum.table: + return <HistoryTable entries={entries} onPinClick={onPinClick} pageIndex={pageIndex} + onLastPageIndexChange={setLastPageIndex}/> + } + } + + return ( + <Fragment> + {mapViewStateToComponent(viewState)} + <Row className="justify-content-center"> + <PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex} + onPageChange={setPageIndex}/> + </Row> + </Fragment>) +} diff --git a/src/components/landing/pages/history/history-table/history-table-row.tsx b/src/components/landing/pages/history/history-table/history-table-row.tsx index 75cc1f4a3..3c03663bb 100644 --- a/src/components/landing/pages/history/history-table/history-table-row.tsx +++ b/src/components/landing/pages/history/history-table/history-table-row.tsx @@ -1,30 +1,30 @@ -import React from "react"; -import {PinButton} from "../common/pin-button"; -import {CloseButton} from "../common/close-button"; -import {useTranslation} from "react-i18next"; -import {HistoryEntryProps} from "../history-content/history-content"; -import {formatHistoryDate} from "../../../../../utils/historyUtils"; -import {Badge} from "react-bootstrap"; +import React from 'react' +import { PinButton } from '../common/pin-button' +import { CloseButton } from '../common/close-button' +import { useTranslation } from 'react-i18next' +import { HistoryEntryProps } from '../history-content/history-content' +import { formatHistoryDate } from '../../../../../utils/historyUtils' +import { Badge } from 'react-bootstrap' -export const HistoryTableRow: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => { - useTranslation() - return ( - <tr> - <td>{entry.title}</td> - <td>{formatHistoryDate(entry.lastVisited)}</td> - <td> - { - entry.tags.map((tag) => <Badge variant={"dark"} className={"mr-1 mb-1"} - key={tag}>{tag}</Badge>) - } - </td> - <td> - <PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => { - onPinClick(entry.id) - }}/> - - <CloseButton isDark={true}/> - </td> - </tr> - ) +export const HistoryTableRow: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => { + useTranslation() + return ( + <tr> + <td>{entry.title}</td> + <td>{formatHistoryDate(entry.lastVisited)}</td> + <td> + { + entry.tags.map((tag) => <Badge variant={'dark'} className={'mr-1 mb-1'} + key={tag}>{tag}</Badge>) + } + </td> + <td> + <PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => { + onPinClick(entry.id) + }}/> + + <CloseButton isDark={true}/> + </td> + </tr> + ) } diff --git a/src/components/landing/pages/history/history-table/history-table.tsx b/src/components/landing/pages/history/history-table/history-table.tsx index abc041a36..c927a2e19 100644 --- a/src/components/landing/pages/history/history-table/history-table.tsx +++ b/src/components/landing/pages/history/history-table/history-table.tsx @@ -1,33 +1,33 @@ -import React from "react"; -import {Table} from "react-bootstrap" -import {HistoryTableRow} from "./history-table-row"; -import {HistoryEntriesProps} from "../history-content/history-content"; -import {Trans} from "react-i18next"; -import {Pager} from "../../../../pagination/pager"; +import React from 'react' +import { Table } from 'react-bootstrap' +import { HistoryTableRow } from './history-table-row' +import { HistoryEntriesProps } from '../history-content/history-content' +import { Trans } from 'react-i18next' +import { Pager } from '../../../../pagination/pager' -export const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => { - return ( - <Table striped bordered hover size="sm" variant="dark"> - <thead> - <tr> - <th><Trans i18nKey={"title"}/></th> - <th><Trans i18nKey={"lastVisit"}/></th> - <th><Trans i18nKey={"tags"}/></th> - <th><Trans i18nKey={"actions"}/></th> - </tr> - </thead> - <tbody> - <Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}> - { - entries.map((entry) => - <HistoryTableRow - key={entry.id} - entry={entry} - onPinClick={onPinClick} - />) - } - </Pager> - </tbody> - </Table> - ) +export const HistoryTable: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => { + return ( + <Table striped bordered hover size="sm" variant="dark"> + <thead> + <tr> + <th><Trans i18nKey={'title'}/></th> + <th><Trans i18nKey={'lastVisit'}/></th> + <th><Trans i18nKey={'tags'}/></th> + <th><Trans i18nKey={'actions'}/></th> + </tr> + </thead> + <tbody> + <Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}> + { + entries.map((entry) => + <HistoryTableRow + key={entry.id} + entry={entry} + onPinClick={onPinClick} + />) + } + </Pager> + </tbody> + </Table> + ) } diff --git a/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx b/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx index e2b8c4b7d..7305710f6 100644 --- a/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx +++ b/src/components/landing/pages/history/history-toolbar/history-toolbar.tsx @@ -1,19 +1,19 @@ -import {Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup} from "react-bootstrap"; -import React, {ChangeEvent, useEffect, useState} from "react"; -import {Trans, useTranslation} from "react-i18next"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {SortButton, SortModeEnum} from "../../../../sort-button/sort-button"; -import {Typeahead} from 'react-bootstrap-typeahead'; -import "./typeahead-hacks.scss"; +import React, { ChangeEvent, useEffect, useState } from 'react' +import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { SortButton, SortModeEnum } from '../../../../sort-button/sort-button' +import { Typeahead } from 'react-bootstrap-typeahead' +import './typeahead-hacks.scss' export type HistoryToolbarChange = (settings: HistoryToolbarState) => void; export interface HistoryToolbarState { - viewState: ViewStateEnum - titleSortDirection: SortModeEnum - lastVisitedSortDirection: SortModeEnum - keywordSearch: string - selectedTags: string[] + viewState: ViewStateEnum + titleSortDirection: SortModeEnum + lastVisitedSortDirection: SortModeEnum + keywordSearch: string + selectedTags: string[] } export enum ViewStateEnum { @@ -27,102 +27,101 @@ export interface HistoryToolbarProps { } export const initState: HistoryToolbarState = { - viewState: ViewStateEnum.card, - titleSortDirection: SortModeEnum.no, - lastVisitedSortDirection: SortModeEnum.no, - keywordSearch: "", - selectedTags: [] + viewState: ViewStateEnum.card, + titleSortDirection: SortModeEnum.no, + lastVisitedSortDirection: SortModeEnum.no, + keywordSearch: '', + selectedTags: [] } -export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({onSettingsChange, tags}) => { +export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange, tags }) => { + const [t] = useTranslation() + const [state, setState] = useState<HistoryToolbarState>(initState) - const [t] = useTranslation() - const [state, setState] = useState<HistoryToolbarState>(initState); + const titleSortChanged = (direction: SortModeEnum) => { + setState(prevState => ({ + ...prevState, + titleSortDirection: direction, + lastVisitedSortDirection: SortModeEnum.no + })) + } - const titleSortChanged = (direction: SortModeEnum) => { - setState(prevState => ({ - ...prevState, - titleSortDirection: direction, - lastVisitedSortDirection: SortModeEnum.no - })) - } + const lastVisitedSortChanged = (direction: SortModeEnum) => { + setState(prevState => ({ + ...prevState, + lastVisitedSortDirection: direction, + titleSortDirection: SortModeEnum.no + })) + } - const lastVisitedSortChanged = (direction: SortModeEnum) => { - setState(prevState => ({ - ...prevState, - lastVisitedSortDirection: direction, - titleSortDirection: SortModeEnum.no - })) - } + const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => { + setState(prevState => ({ ...prevState, keywordSearch: event.currentTarget.value })) + } - const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => { - setState(prevState => ({...prevState, keywordSearch: event.currentTarget.value})); - } + const toggleViewChanged = (newViewState: ViewStateEnum) => { + setState((prevState) => ({ ...prevState, viewState: newViewState })) + } - const toggleViewChanged = (newViewState: ViewStateEnum) => { - setState((prevState) => ({...prevState, viewState: newViewState})) - } + const selectedTagsChanged = (selected: string[]) => { + setState(prevState => ({ ...prevState, selectedTags: selected })) + } - const selectedTagsChanged = (selected: string[]) => { - setState((prevState => ({...prevState, selectedTags: selected}))) - } + useEffect(() => { + onSettingsChange(state) + }, [onSettingsChange, state]) - useEffect(() => { - onSettingsChange(state); - }, [onSettingsChange, state]) - - return ( - <Form inline={true}> - <InputGroup className={"mr-1"}> - <Typeahead id={"tagsSelection"} options={tags} multiple={true} placeholder={t("selectTags")} - onChange={selectedTagsChanged}/> - </InputGroup> - <InputGroup className={"mr-1"}> - <FormControl - placeholder={t("searchKeywords")} - aria-label={t("searchKeywords")} - onChange={keywordSearchChanged} - /> - </InputGroup> - <InputGroup className={"mr-1"}> - <SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={"light"}><Trans - i18nKey={"sortByTitle"}/></SortButton> - </InputGroup> - <InputGroup className={"mr-1"}> - <SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection} - variant={"light"}><Trans i18nKey={"sortByLastVisited"}/></SortButton> - </InputGroup> - <InputGroup className={"mr-1"}> - <Button variant={"light"} title={t("exportHistory")}> - <FontAwesomeIcon icon={"download"}/> - </Button> - </InputGroup> - <InputGroup className={"mr-1"}> - <Button variant={"light"} title={t("importHistory")}> - <FontAwesomeIcon icon={"upload"}/> - </Button> - </InputGroup> - <InputGroup className={"mr-1"}> - <Button variant={"light"} title={t("clearHistory")}> - <FontAwesomeIcon icon={"trash"}/> - </Button> - </InputGroup> - <InputGroup className={"mr-1"}> - <Button variant={"light"} title={t("refreshHistory")}> - <FontAwesomeIcon icon={"sync"}/> - </Button> - </InputGroup> - <InputGroup className={"mr-1"}> - <ToggleButtonGroup type="radio" name="options" value={state.viewState} - onChange={(newViewState: ViewStateEnum) => { - toggleViewChanged(newViewState) - }}> - <ToggleButton className={"btn-light"} value={ViewStateEnum.card}><Trans - i18nKey={"cards"}/></ToggleButton> - <ToggleButton className={"btn-light"} value={ViewStateEnum.table}><Trans - i18nKey={"table"}/></ToggleButton> - </ToggleButtonGroup> - </InputGroup> - </Form> - ) -} \ No newline at end of file + return ( + <Form inline={true}> + <InputGroup className={'mr-1'}> + <Typeahead id={'tagsSelection'} options={tags} multiple={true} placeholder={t('selectTags')} + onChange={selectedTagsChanged}/> + </InputGroup> + <InputGroup className={'mr-1'}> + <FormControl + placeholder={t('searchKeywords')} + aria-label={t('searchKeywords')} + onChange={keywordSearchChanged} + /> + </InputGroup> + <InputGroup className={'mr-1'}> + <SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={'light'}><Trans + i18nKey={'sortByTitle'}/></SortButton> + </InputGroup> + <InputGroup className={'mr-1'}> + <SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection} + variant={'light'}><Trans i18nKey={'sortByLastVisited'}/></SortButton> + </InputGroup> + <InputGroup className={'mr-1'}> + <Button variant={'light'} title={t('exportHistory')}> + <FontAwesomeIcon icon={'download'}/> + </Button> + </InputGroup> + <InputGroup className={'mr-1'}> + <Button variant={'light'} title={t('importHistory')}> + <FontAwesomeIcon icon={'upload'}/> + </Button> + </InputGroup> + <InputGroup className={'mr-1'}> + <Button variant={'light'} title={t('clearHistory')}> + <FontAwesomeIcon icon={'trash'}/> + </Button> + </InputGroup> + <InputGroup className={'mr-1'}> + <Button variant={'light'} title={t('refreshHistory')}> + <FontAwesomeIcon icon={'sync'}/> + </Button> + </InputGroup> + <InputGroup className={'mr-1'}> + <ToggleButtonGroup type="radio" name="options" value={state.viewState} + onChange={(newViewState: ViewStateEnum) => { + toggleViewChanged(newViewState) + }}> + <ToggleButton className={'btn-light'} value={ViewStateEnum.card}><Trans + i18nKey={'cards'}/></ToggleButton> + <ToggleButton className={'btn-light'} value={ViewStateEnum.table}><Trans + i18nKey={'table'}/></ToggleButton> + </ToggleButtonGroup> + </InputGroup> + </Form> + ) +} diff --git a/src/components/landing/pages/history/history.tsx b/src/components/landing/pages/history/history.tsx index 446b9fac3..ac2d3b127 100644 --- a/src/components/landing/pages/history/history.tsx +++ b/src/components/landing/pages/history/history.tsx @@ -1,67 +1,67 @@ -import React, {Fragment, useEffect, useState} from 'react' -import {HistoryContent} from './history-content/history-content'; -import {HistoryToolbar, HistoryToolbarState, initState as toolbarInitState} from './history-toolbar/history-toolbar'; -import {loadHistoryFromLocalStore, sortAndFilterEntries} from "../../../../utils/historyUtils"; -import {Row} from 'react-bootstrap'; -import {Trans, useTranslation} from "react-i18next"; +import React, { Fragment, useEffect, useState } from 'react' +import { Row } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { loadHistoryFromLocalStore, sortAndFilterEntries } from '../../../../utils/historyUtils' +import { HistoryContent } from './history-content/history-content' +import { HistoryToolbar, HistoryToolbarState, initState as toolbarInitState } from './history-toolbar/history-toolbar' export interface HistoryEntry { - id: string, - title: string, - lastVisited: Date, - tags: string[], - pinned: boolean + id: string, + title: string, + lastVisited: Date, + tags: string[], + pinned: boolean } export type pinClick = (entryId: string) => void; export const History: React.FC = () => { - useTranslation(); - const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([]) - const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState) + useTranslation() + const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([]) + const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState) - useEffect(() => { - const history = loadHistoryFromLocalStore(); - setHistoryEntries(history); - }, []) + useEffect(() => { + const history = loadHistoryFromLocalStore() + setHistoryEntries(history) + }, []) - useEffect(() => { - if (historyEntries === []) { - return; - } - window.localStorage.setItem("history", JSON.stringify(historyEntries)); - }, [historyEntries]) - - const pinClick: pinClick = (entryId: string) => { - setHistoryEntries((entries) => { - return entries.map((entry) => { - if (entry.id === entryId) { - entry.pinned = !entry.pinned; - } - return entry; - }); - }) + useEffect(() => { + if (historyEntries === []) { + return } + window.localStorage.setItem('history', JSON.stringify(historyEntries)) + }, [historyEntries]) - const tags = historyEntries.map(entry => entry.tags) - .reduce((a, b) => ([...a, ...b]), []) - .filter((value, index, array) => { - if (index === 0) { - return true; - } - return (value !== array[index - 1]) - }) - const entriesToShow = sortAndFilterEntries(historyEntries, viewState); + const pinClick: pinClick = (entryId: string) => { + setHistoryEntries((entries) => { + return entries.map((entry) => { + if (entry.id === entryId) { + entry.pinned = !entry.pinned + } + return entry + }) + }) + } - return ( - <Fragment> - <h1 className="mb-4"><Trans i18nKey="history"/></h1> - <Row className={"justify-content-center mb-3"}> - <HistoryToolbar onSettingsChange={setViewState} tags={tags}/> - </Row> - <HistoryContent viewState={viewState.viewState} - entries={entriesToShow} - onPinClick={pinClick}/> - </Fragment> - ) + const tags = historyEntries.map(entry => entry.tags) + .reduce((a, b) => ([...a, ...b]), []) + .filter((value, index, array) => { + if (index === 0) { + return true + } + return (value !== array[index - 1]) + }) + const entriesToShow = sortAndFilterEntries(historyEntries, viewState) + + return ( + <Fragment> + <h1 className="mb-4"><Trans i18nKey="history"/></h1> + <Row className={'justify-content-center mb-3'}> + <HistoryToolbar onSettingsChange={setViewState} tags={tags}/> + </Row> + <HistoryContent viewState={viewState.viewState} + entries={entriesToShow} + onPinClick={pinClick}/> + </Fragment> + ) } diff --git a/src/components/landing/pages/intro/cover-buttons/cover-buttons.tsx b/src/components/landing/pages/intro/cover-buttons/cover-buttons.tsx index 27775430e..48604f21b 100644 --- a/src/components/landing/pages/intro/cover-buttons/cover-buttons.tsx +++ b/src/components/landing/pages/intro/cover-buttons/cover-buttons.tsx @@ -1,46 +1,45 @@ -import {LoginStatus} from "../../../../../redux/user/types"; -import {Link} from "react-router-dom"; -import {Button} from "react-bootstrap"; -import {Trans, useTranslation} from "react-i18next"; -import React from "react"; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../../redux"; -import "./cover-buttons.scss"; +import React from 'react' +import { Link } from 'react-router-dom' +import { Button } from 'react-bootstrap' +import { LoginStatus } from '../../../../../redux/user/types' +import { Trans, useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../../redux' +import './cover-buttons.scss' export const CoverButtons: React.FC = () => { - useTranslation(); - const user = useSelector((state: ApplicationState) => state.user); + useTranslation() + const user = useSelector((state: ApplicationState) => state.user) - if (user.status === LoginStatus.ok) { - return null; - } + if (user.status === LoginStatus.ok) { + return null + } - return ( - <div className="mb-5"> - <Link to="/login"> - <Button - className="cover-button" - variant="success" - size="lg" - > - <Trans i18nKey="signIn"/> - </Button> - </Link> + return ( + <div className="mb-5"> + <Link to="/login"> + <Button + className="cover-button" + variant="success" + size="lg" + > + <Trans i18nKey="signIn"/> + </Button> + </Link> - <span className="m-2"> - <Trans i18nKey="or"/> - </span> - - <Link to="/features"> - <Button - className="cover-button" - variant="primary" - size="lg" - > - <Trans i18nKey="exploreFeatures"/> - </Button> - </Link> - </div> - ); + <span className="m-2"> + <Trans i18nKey="or"/> + </span> + <Link to="/features"> + <Button + className="cover-button" + variant="primary" + size="lg" + > + <Trans i18nKey="exploreFeatures"/> + </Button> + </Link> + </div> + ) } diff --git a/src/components/landing/pages/intro/feature-links.tsx b/src/components/landing/pages/intro/feature-links.tsx index 9b50c9310..53380034a 100644 --- a/src/components/landing/pages/intro/feature-links.tsx +++ b/src/components/landing/pages/intro/feature-links.tsx @@ -1,37 +1,37 @@ -import {Col, Row} from "react-bootstrap"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {Trans, useTranslation} from "react-i18next"; -import React from "react"; -import { Link } from "react-router-dom"; +import React from 'react' +import { Link } from 'react-router-dom' +import { Col, Row } from 'react-bootstrap' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Trans, useTranslation } from 'react-i18next' export const FeatureLinks: React.FC = () => { - useTranslation(); - return ( - <Row className="mb-5"> - <Col md={4}> - <Link to={"/features#Share-Notes"} className="text-light"> - <FontAwesomeIcon icon="bolt" size="3x"/> - <h5> - <Trans i18nKey="featureCollaboration"/> - </h5> - </Link> - </Col> - <Col md={4}> - <Link to={"/features#MathJax"} className="text-light"> - <FontAwesomeIcon icon="chart-bar" size="3x"/> - <h5> - <Trans i18nKey="featureMathJax"/> - </h5> - </Link> - </Col> - <Col md={4}> - <Link to={"/features#Slide-Mode"} className="text-light"> - <FontAwesomeIcon icon="tv" size="3x"/> - <h5> - <Trans i18nKey="featureSlides"/> - </h5> - </Link> - </Col> - </Row> - ); + useTranslation() + return ( + <Row className="mb-5"> + <Col md={4}> + <Link to={'/features#Share-Notes'} className="text-light"> + <FontAwesomeIcon icon="bolt" size="3x"/> + <h5> + <Trans i18nKey="featureCollaboration"/> + </h5> + </Link> + </Col> + <Col md={4}> + <Link to={'/features#MathJax'} className="text-light"> + <FontAwesomeIcon icon="chart-bar" size="3x"/> + <h5> + <Trans i18nKey="featureMathJax"/> + </h5> + </Link> + </Col> + <Col md={4}> + <Link to={'/features#Slide-Mode'} className="text-light"> + <FontAwesomeIcon icon="tv" size="3x"/> + <h5> + <Trans i18nKey="featureSlides"/> + </h5> + </Link> + </Col> + </Row> + ) } diff --git a/src/components/landing/pages/intro/intro.tsx b/src/components/landing/pages/intro/intro.tsx index ad0154c6a..74afe841c 100644 --- a/src/components/landing/pages/intro/intro.tsx +++ b/src/components/landing/pages/intro/intro.tsx @@ -1,29 +1,28 @@ import React from 'react' -import screenshot from './img/screenshot.png'; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {Trans, useTranslation} from "react-i18next"; -import {FeatureLinks} from "./feature-links"; -import {CoverButtons} from "./cover-buttons/cover-buttons"; +import screenshot from './img/screenshot.png' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Trans, useTranslation } from 'react-i18next' +import { FeatureLinks } from './feature-links' +import { CoverButtons } from './cover-buttons/cover-buttons' const Intro: React.FC = () => { - useTranslation(); + useTranslation() + return ( + <div> + <h1> + <FontAwesomeIcon icon="file-alt"/> CodiMD + </h1> + <p className="lead mb-5"> + <Trans i18nKey="coverSlogan"/> + </p> - return ( - <div> - <h1> - <FontAwesomeIcon icon="file-alt"/> CodiMD - </h1> - <p className="lead mb-5"> - <Trans i18nKey="coverSlogan"/> - </p> + <CoverButtons/> - <CoverButtons/> - - <img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/> - <FeatureLinks/> - </div> - ) + <img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/> + <FeatureLinks/> + </div> + ) } -export {Intro} +export { Intro } diff --git a/src/components/landing/pages/login/auth/social-link-button/social-link-button.tsx b/src/components/landing/pages/login/auth/social-link-button/social-link-button.tsx index 1594a2b8a..9a99aa806 100644 --- a/src/components/landing/pages/login/auth/social-link-button/social-link-button.tsx +++ b/src/components/landing/pages/login/auth/social-link-button/social-link-button.tsx @@ -1,25 +1,25 @@ -import React from "react"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import "./social-link-button.scss"; -import {IconProp} from "@fortawesome/fontawesome-svg-core"; +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import './social-link-button.scss' +import { IconProp } from '../../../../../../utils/iconProp' export interface SocialButtonProps { - backgroundClass: string, - href: string - icon: IconProp - title?: string + backgroundClass: string, + href: string + icon: IconProp + title?: string } -export const SocialLinkButton: React.FC<SocialButtonProps> = ({title, backgroundClass, href, icon, children}) => { - return ( - <a href={href} title={title} - className={"btn social-link-button p-0 d-inline-flex align-items-stretch " + backgroundClass}> - <span className="icon-part d-flex align-items-center"> - <FontAwesomeIcon icon={icon} className={"social-icon"} fixedWidth={true}/> - </span> - <span className="text-part d-flex align-items-center mx-auto"> - {children} - </span> - </a> - ) +export const SocialLinkButton: React.FC<SocialButtonProps> = ({ title, backgroundClass, href, icon, children }) => { + return ( + <a href={href} title={title} + className={'btn social-link-button p-0 d-inline-flex align-items-stretch ' + backgroundClass}> + <span className="icon-part d-flex align-items-center"> + <FontAwesomeIcon icon={icon} className={'social-icon'} fixedWidth={true}/> + </span> + <span className="text-part d-flex align-items-center mx-auto"> + {children} + </span> + </a> + ) } diff --git a/src/components/landing/pages/login/auth/via-email.tsx b/src/components/landing/pages/login/auth/via-email.tsx index 412f3c062..2ce0a1662 100644 --- a/src/components/landing/pages/login/auth/via-email.tsx +++ b/src/components/landing/pages/login/auth/via-email.tsx @@ -1,72 +1,64 @@ -import {Trans, useTranslation} from "react-i18next"; -import {Alert, Button, Card, Form} from "react-bootstrap"; -import React, {useState} from "react"; -import {postEmailLogin} from "../../../../../api/user"; -import {getAndSetUser} from "../../../../../utils/apiUtils"; +import { Trans, useTranslation } from 'react-i18next' +import { Alert, Button, Card, Form } from 'react-bootstrap' +import React, { FormEvent, useState } from 'react' +import { postEmailLogin } from '../../../../../api/user' +import { getAndSetUser } from '../../../../../utils/apiUtils' export const ViaEMail: React.FC = () => { - const {t} = useTranslation(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(false); + const { t } = useTranslation() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(false) - const doAsyncLogin = () => { - (async () => { - try { - await postEmailLogin(email, password); - await getAndSetUser(); - } catch { - setError(true); - } - })(); - } + const doAsyncLogin = async () => { + await postEmailLogin(email, password) + await getAndSetUser() + } - const onFormSubmit = (event: any) => { - doAsyncLogin(); - event.preventDefault(); - }; + const onFormSubmit = (event: FormEvent) => { + doAsyncLogin().catch(() => setError(true)) + event.preventDefault() + } - return ( - <Card className="bg-dark mb-4"> - <Card.Body> - <Card.Title> - <Trans i18nKey="signInVia" values={{service: "E-Mail"}}/> - </Card.Title> + return ( + <Card className="bg-dark mb-4"> + <Card.Body> + <Card.Title> + <Trans i18nKey="signInVia" values={{ service: 'E-Mail' }}/> + </Card.Title> + <Form onSubmit={onFormSubmit}> + <Form.Group controlId="email"> + <Form.Control + isInvalid={error} + type="email" + size="sm" + placeholder={t('email')} + onChange={(event) => setEmail(event.currentTarget.value)} className="bg-dark text-white" + /> + </Form.Group> - <Form onSubmit={onFormSubmit}> - <Form.Group controlId="email"> - <Form.Control - isInvalid={error} - type="email" - size="sm" - placeholder={t("email")} - onChange={(event) => setEmail(event.currentTarget.value)} - className="bg-dark text-white" - /> - </Form.Group> + <Form.Group controlId="password"> + <Form.Control + isInvalid={error} + type="password" + size="sm" + placeholder={t('password')} + onChange={(event) => setPassword(event.currentTarget.value)} + className="bg-dark text-white"/> + </Form.Group> - <Form.Group controlId="password"> - <Form.Control - isInvalid={error} - type="password" - size="sm" - placeholder={t("password")} - onChange={(event) => setPassword(event.currentTarget.value)} - className="bg-dark text-white" - /> - </Form.Group> + <Alert className="small" show={error} variant="danger"> + <Trans i18nKey="errorEmailLogin"/> + </Alert> - <Alert className="small" show={error} variant="danger"> - <Trans i18nKey="errorEmailLogin"/> - </Alert> + <Button + type="submit" - <Button - type="submit" - variant="primary"> - <Trans i18nKey="signIn"/> - </Button> - </Form> - </Card.Body> - </Card> - ); -} \ No newline at end of file + variant="primary"> + <Trans i18nKey="signIn"/> + </Button> + </Form> + </Card.Body> + </Card> + ) +} diff --git a/src/components/landing/pages/login/auth/via-ldap.tsx b/src/components/landing/pages/login/auth/via-ldap.tsx index 716b70796..1815d68e1 100644 --- a/src/components/landing/pages/login/auth/via-ldap.tsx +++ b/src/components/landing/pages/login/auth/via-ldap.tsx @@ -1,80 +1,74 @@ -import React, {useState} from "react"; -import {Trans, useTranslation} from "react-i18next"; -import {Alert, Button, Card, Form} from "react-bootstrap"; -import {postLdapLogin} from "../../../../../api/user"; -import {getAndSetUser} from "../../../../../utils/apiUtils"; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../../redux"; +import React, { FormEvent, useState } from 'react' -const ViaLdap: React.FC = () => { - const {t} = useTranslation(); - const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap); +import { Trans, useTranslation } from 'react-i18next' +import { Alert, Button, Card, Form } from 'react-bootstrap' +import { postLdapLogin } from '../../../../../api/user' +import { getAndSetUser } from '../../../../../utils/apiUtils' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../../redux' - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(false); +export const ViaLdap: React.FC = () => { + const { t } = useTranslation() + const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap) - const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : "LDAP"; + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(false) - const doAsyncLogin = () => { - (async () => { - try { - await postLdapLogin(username, password); - await getAndSetUser(); - } catch { - setError(true); - } - })(); + const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP' + + const doAsyncLogin = async () => { + try { + await postLdapLogin(username, password) + await getAndSetUser() + } catch { + setError(true) } + } - const onFormSubmit = (event: any) => { - doAsyncLogin(); - event.preventDefault(); - } + const onFormSubmit = (event: FormEvent) => { + doAsyncLogin().catch(() => setError(true)) + event.preventDefault() + } - return ( - <Card className="bg-dark mb-4"> - <Card.Body> - <Card.Title> - <Trans i18nKey="signInVia" values={{service: name}}/> - </Card.Title> + return ( + <Card className="bg-dark mb-4"> + <Card.Body> + <Card.Title> + <Trans i18nKey="signInVia" values={{ service: name }}/> + </Card.Title> + <Form onSubmit={onFormSubmit}> + <Form.Group controlId="username"> + <Form.Control + isInvalid={error} + type="text" + size="sm" + placeholder={t('username')} + onChange={(event) => setUsername(event.currentTarget.value)} className="bg-dark text-white" + /> + </Form.Group> - <Form onSubmit={onFormSubmit}> - <Form.Group controlId="username"> - <Form.Control - isInvalid={error} - type="text" - size="sm" - placeholder={t("username")} - onChange={(event) => setUsername(event.currentTarget.value)} - className="bg-dark text-white" - /> - </Form.Group> + <Form.Group controlId="password"> + <Form.Control + isInvalid={error} + type="password" + size="sm" + placeholder={t('password')} + onChange={(event) => setPassword(event.currentTarget.value)} + className="bg-dark text-white"/> + </Form.Group> - <Form.Group controlId="password"> - <Form.Control - isInvalid={error} - type="password" - size="sm" - placeholder={t("password")} - onChange={(event) => setPassword(event.currentTarget.value)} - className="bg-dark text-white" - /> - </Form.Group> + <Alert className="small" show={error} variant="danger"> + <Trans i18nKey="errorLdapLogin"/> + </Alert> - <Alert className="small" show={error} variant="danger"> - <Trans i18nKey="errorLdapLogin"/> - </Alert> - - <Button - type="submit" - variant="primary"> - <Trans i18nKey="signIn"/> - </Button> - </Form> - </Card.Body> - </Card> - ); -}; - -export { ViaLdap } + <Button + type="submit" + variant="primary"> + <Trans i18nKey="signIn"/> + </Button> + </Form> + </Card.Body> + </Card> + ) +} diff --git a/src/components/landing/pages/login/auth/via-one-click.tsx b/src/components/landing/pages/login/auth/via-one-click.tsx index 9425af071..a458d9ab6 100644 --- a/src/components/landing/pages/login/auth/via-one-click.tsx +++ b/src/components/landing/pages/login/auth/via-one-click.tsx @@ -1,115 +1,115 @@ -import React from "react"; -import {IconProp} from "@fortawesome/fontawesome-svg-core"; -import {SocialLinkButton} from "./social-link-button/social-link-button"; +import React from 'react' +import { IconProp } from '../../../../../utils/iconProp' +import { SocialLinkButton } from './social-link-button/social-link-button' export enum OneClickType { - 'DROPBOX'="dropbox", - 'FACEBOOK'="facebook", - 'GITHUB'="github", - 'GITLAB'="gitlab", - 'GOOGLE'="google", - 'OAUTH2'="oauth2", - 'SAML'="saml", - 'TWITTER'="twitter" + 'DROPBOX' = 'dropbox', + 'FACEBOOK' = 'facebook', + 'GITHUB' = 'github', + 'GITLAB' = 'gitlab', + 'GOOGLE' = 'google', + 'OAUTH2' = 'oauth2', + 'SAML' = 'saml', + 'TWITTER' = 'twitter' } type OneClick2Map = (oneClickType: OneClickType) => { - name: string, - icon: IconProp, - className: string, - url: string + name: string, + icon: IconProp, + className: string, + url: string }; const buildBackendAuthUrl = (backendName: string) => { - return `https://localhost:3000/auth/${backendName}` -}; + return `https://localhost:3000/auth/${backendName}` +} const getMetadata: OneClick2Map = (oneClickType: OneClickType) => { - switch (oneClickType) { - case OneClickType.DROPBOX: - return { - name: "Dropbox", - icon: ["fab", "dropbox"], - className: "btn-social-dropbox", - url: buildBackendAuthUrl("dropbox") - } - case OneClickType.FACEBOOK: - return { - name: "Facebook", - icon: ["fab", "facebook"], - className: "btn-social-facebook", - url: buildBackendAuthUrl("facebook") - } - case OneClickType.GITHUB: - return { - name: "GitHub", - icon: ["fab", "github"], - className: "btn-social-github", - url: buildBackendAuthUrl("github") - } - case OneClickType.GITLAB: - return { - name: "GitLab", - icon: ["fab", "gitlab"], - className: "btn-social-gitlab", - url: buildBackendAuthUrl("gitlab") - } - case OneClickType.GOOGLE: - return { - name: "Google", - icon: ["fab", "google"], - className: "btn-social-google", - url: buildBackendAuthUrl("google") - } - case OneClickType.OAUTH2: - return { - name: "OAuth2", - icon: "address-card", - className: "btn-primary", - url: buildBackendAuthUrl("oauth2") - } - case OneClickType.SAML: - return { - name: "SAML", - icon: "users", - className: "btn-success", - url: buildBackendAuthUrl("saml") - } - case OneClickType.TWITTER: - return { - name: "Twitter", - icon: ["fab", "twitter"], - className: "btn-social-twitter", - url: buildBackendAuthUrl("twitter") - } - default: - return { - name: "", - icon: "exclamation", - className: "", - url: "#" - } - } + switch (oneClickType) { + case OneClickType.DROPBOX: + return { + name: 'Dropbox', + icon: ['fab', 'dropbox'], + className: 'btn-social-dropbox', + url: buildBackendAuthUrl('dropbox') + } + case OneClickType.FACEBOOK: + return { + name: 'Facebook', + icon: ['fab', 'facebook'], + className: 'btn-social-facebook', + url: buildBackendAuthUrl('facebook') + } + case OneClickType.GITHUB: + return { + name: 'GitHub', + icon: ['fab', 'github'], + className: 'btn-social-github', + url: buildBackendAuthUrl('github') + } + case OneClickType.GITLAB: + return { + name: 'GitLab', + icon: ['fab', 'gitlab'], + className: 'btn-social-gitlab', + url: buildBackendAuthUrl('gitlab') + } + case OneClickType.GOOGLE: + return { + name: 'Google', + icon: ['fab', 'google'], + className: 'btn-social-google', + url: buildBackendAuthUrl('google') + } + case OneClickType.OAUTH2: + return { + name: 'OAuth2', + icon: 'address-card', + className: 'btn-primary', + url: buildBackendAuthUrl('oauth2') + } + case OneClickType.SAML: + return { + name: 'SAML', + icon: 'users', + className: 'btn-success', + url: buildBackendAuthUrl('saml') + } + case OneClickType.TWITTER: + return { + name: 'Twitter', + icon: ['fab', 'twitter'], + className: 'btn-social-twitter', + url: buildBackendAuthUrl('twitter') + } + default: + return { + name: '', + icon: 'exclamation', + className: '', + url: '#' + } + } } export interface ViaOneClickProps { - oneClickType: OneClickType; - optionalName?: string; + oneClickType: OneClickType; + optionalName?: string; } -const ViaOneClick: React.FC<ViaOneClickProps> = ({oneClickType, optionalName}) => { - const {name, icon, className, url} = getMetadata(oneClickType); - const text = !!optionalName ? optionalName : name; - return ( - <SocialLinkButton - backgroundClass={className} - icon={icon} - href={url} - title={text} - > - {text} - </SocialLinkButton> - ) +const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => { + const { name, icon, className, url } = getMetadata(oneClickType) + const text = optionalName || name + return ( + <SocialLinkButton + backgroundClass={className} + icon={icon} + href={url} + title={text} + > + {text} + </SocialLinkButton> + ) } -export {ViaOneClick} +export { ViaOneClick } diff --git a/src/components/landing/pages/login/auth/via-openid.tsx b/src/components/landing/pages/login/auth/via-openid.tsx index 3883e9acd..307d57379 100644 --- a/src/components/landing/pages/login/auth/via-openid.tsx +++ b/src/components/landing/pages/login/auth/via-openid.tsx @@ -1,61 +1,53 @@ -import React, {useState} from "react"; -import {Trans, useTranslation} from "react-i18next"; -import {Alert, Button, Card, Form} from "react-bootstrap"; -import {postOpenIdLogin} from "../../../../../api/user"; -import {getAndSetUser} from "../../../../../utils/apiUtils"; +import React, { FormEvent, useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { Alert, Button, Card, Form } from 'react-bootstrap' +import { postOpenIdLogin } from '../../../../../api/user' +import { getAndSetUser } from '../../../../../utils/apiUtils' -const ViaOpenId: React.FC = () => { - useTranslation(); - const [openId, setOpenId] = useState(""); - const [error, setError] = useState(false); - const doAsyncLogin = () => { - (async () => { - try { - await postOpenIdLogin(openId); - await getAndSetUser(); - } catch { - setError(true); - } - })(); - } +export const ViaOpenId: React.FC = () => { + useTranslation() + const [openId, setOpenId] = useState('') + const [error, setError] = useState(false) + const doAsyncLogin: (() => Promise<void>) = async () => { + await postOpenIdLogin(openId) + await getAndSetUser() + } - const onFormSubmit = (event: any) => { - doAsyncLogin(); - event.preventDefault(); - } + const onFormSubmit = (event: FormEvent) => { + doAsyncLogin().catch(() => setError(true)) + event.preventDefault() + } - return ( - <Card className="bg-dark mb-4"> - <Card.Body> - <Card.Title> - <Trans i18nKey="signInVia" values={{service: "OpenID"}}/> - </Card.Title> + return ( + <Card className="bg-dark mb-4"> + <Card.Body> + <Card.Title> + <Trans i18nKey="signInVia" values={{ service: 'OpenID' }}/> + </Card.Title> - <Form onSubmit={onFormSubmit}> - <Form.Group controlId="openid"> - <Form.Control - isInvalid={error} - type="text" - size="sm" - placeholder={"OpenID"} - onChange={(event) => setOpenId(event.currentTarget.value)} - className="bg-dark text-white" - /> - </Form.Group> + <Form onSubmit={onFormSubmit}> + <Form.Group controlId="openid"> + <Form.Control + isInvalid={error} + type="text" + size="sm" + placeholder={'OpenID'} + onChange={(event) => setOpenId(event.currentTarget.value)} + className="bg-dark text-white" + /> + </Form.Group> - <Alert className="small" show={error} variant="danger"> - <Trans i18nKey="errorOpenIdLogin"/> - </Alert> + <Alert className="small" show={error} variant="danger"> + <Trans i18nKey="errorOpenIdLogin"/> + </Alert> - <Button - type="submit" - variant="primary"> - <Trans i18nKey="signIn"/> - </Button> - </Form> - </Card.Body> - </Card> - ); -}; - -export { ViaOpenId } + <Button + type="submit" + variant="primary"> + <Trans i18nKey="signIn"/> + </Button> + </Form> + </Card.Body> + </Card> + ) +} diff --git a/src/components/landing/pages/login/login.tsx b/src/components/landing/pages/login/login.tsx index 2ee5547cc..5ea95d6c9 100644 --- a/src/components/landing/pages/login/login.tsx +++ b/src/components/landing/pages/login/login.tsx @@ -1,83 +1,81 @@ -import React from "react" -import {Card, Col, Row} from "react-bootstrap" -import {Trans, useTranslation} from "react-i18next"; -import {ViaEMail} from "./auth/via-email"; -import {OneClickType, ViaOneClick} from "./auth/via-one-click"; -import {ViaLdap} from "./auth/via-ldap"; -import {useSelector} from "react-redux"; -import {ApplicationState} from "../../../../redux"; -import {ViaOpenId} from "./auth/via-openid"; -import {Redirect} from "react-router"; -import {LoginStatus} from "../../../../redux/user/types"; +import React from 'react' +import { Card, Col, Row } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { ViaEMail } from './auth/via-email' +import { OneClickType, ViaOneClick } from './auth/via-one-click' +import { ViaLdap } from './auth/via-ldap' +import { useSelector } from 'react-redux' +import { ApplicationState } from '../../../../redux' +import { ViaOpenId } from './auth/via-openid' +import { Redirect } from 'react-router' +import { LoginStatus } from '../../../../redux/user/types' -const Login: React.FC = () => { - useTranslation(); - const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders); - const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames); - const userLoginState = useSelector((state: ApplicationState) => state.user.status); - const emailForm = authProviders.email ? <ViaEMail/> : null - const ldapForm = authProviders.ldap ? <ViaLdap/> : null - const openIdForm = authProviders.openid ? <ViaOpenId/> : null +export const Login: React.FC = () => { + useTranslation() + const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders) + const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames) + const userLoginState = useSelector((state: ApplicationState) => state.user.status) + const emailForm = authProviders.email ? <ViaEMail/> : null + const ldapForm = authProviders.ldap ? <ViaLdap/> : null + const openIdForm = authProviders.openid ? <ViaOpenId/> : null - const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => { - switch (type) { - case OneClickType.SAML: - return customAuthNames.saml; - case OneClickType.OAUTH2: - return customAuthNames.oauth2; - default: - return undefined; - } - } - - if (userLoginState === LoginStatus.ok) { - // TODO Redirect to previous page? - return ( - <Redirect to='/history' /> - ) + const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => { + switch (type) { + case OneClickType.SAML: + return customAuthNames.saml + case OneClickType.OAUTH2: + return customAuthNames.oauth2 + default: + return undefined } + } + if (userLoginState === LoginStatus.ok) { + // TODO Redirect to previous page? return ( - <div className="my-3"> - <Row className="h-100 flex justify-content-center"> - { - authProviders.email || authProviders.ldap || authProviders.openid ? - <Col xs={12} sm={10} lg={4}> - {emailForm} - {ldapForm} - {openIdForm} - </Col> - : null - } - <Col xs={12} sm={10} lg={4}> - <Card className="bg-dark mb-4"> - <Card.Body> - <Card.Title> - <Trans i18nKey="signInVia" values={{service: ""}}/> - </Card.Title> - { - Object.values(OneClickType) - .filter((value) => authProviders[value]) - .map((value) => { - return ( - <div - className="p-2 d-flex flex-column social-button-container" - key={value} - > - <ViaOneClick - oneClickType={value} - optionalName={oneClickCustomName(value)} - /> - </div> - ) - }) - } - </Card.Body> - </Card> - </Col> - </Row> - </div> + <Redirect to='/history'/> ) -} + } -export {Login} + return ( + <div className="my-3"> + <Row className="h-100 flex justify-content-center"> + { + authProviders.email || authProviders.ldap || authProviders.openid + ? <Col xs={12} sm={10} lg={4}> + {emailForm} + {ldapForm} + {openIdForm} + </Col> + : null + } + <Col xs={12} sm={10} lg={4}> + <Card className="bg-dark mb-4"> + <Card.Body> + <Card.Title> + <Trans i18nKey="signInVia" values={{ service: '' }}/> + </Card.Title> + { + Object.values(OneClickType) + .filter((value) => authProviders[value]) + .map((value) => { + return ( + <div + className="p-2 d-flex flex-column social-button-container" + key={value} + > + <ViaOneClick + oneClickType={value} + optionalName={oneClickCustomName(value)} + /> + </div> + ) + }) + } + </Card.Body> + </Card> + </Col> + </Row> + </div> + ) +} diff --git a/src/components/pagination/pager-item.tsx b/src/components/pagination/pager-item.tsx index 40f579668..0ab6089dd 100644 --- a/src/components/pagination/pager-item.tsx +++ b/src/components/pagination/pager-item.tsx @@ -1,17 +1,16 @@ -import React from "react"; +import React from 'react' export interface PageItemProps { onClick: (index: number) => void index: number } - -export const PagerItem: React.FC<PageItemProps> = ({index, onClick}) => { - return ( - <li className="page-item"> - <span className="page-link" role="button" onClick={() => onClick(index)}> - {index + 1} - </span> - </li> - ); -} \ No newline at end of file +export const PagerItem: React.FC<PageItemProps> = ({ index, onClick }) => { + return ( + <li className="page-item"> + <span className="page-link" role="button" onClick={() => onClick(index)}> + {index + 1} + </span> + </li> + ) +} diff --git a/src/components/pagination/pager-pagination.tsx b/src/components/pagination/pager-pagination.tsx index 72accb68f..a8009fc8e 100644 --- a/src/components/pagination/pager-pagination.tsx +++ b/src/components/pagination/pager-pagination.tsx @@ -1,6 +1,6 @@ -import React, {Fragment, useEffect, useState} from "react"; -import {Pagination} from "react-bootstrap"; -import {PagerItem} from "./pager-item"; +import React, { Fragment, useEffect, useState } from 'react' +import { Pagination } from 'react-bootstrap' +import { PagerItem } from './pager-item' export interface PaginationProps { numberOfPageButtonsToShowAfterAndBeforeCurrent: number @@ -8,75 +8,75 @@ export interface PaginationProps { lastPageIndex: number } -export const PagerPagination: React.FC<PaginationProps> = ({numberOfPageButtonsToShowAfterAndBeforeCurrent, onPageChange, lastPageIndex}) => { - if (numberOfPageButtonsToShowAfterAndBeforeCurrent % 2 !== 0) { - throw new Error("number of pages to show must be even!") - } +export const PagerPagination: React.FC<PaginationProps> = ({ numberOfPageButtonsToShowAfterAndBeforeCurrent, onPageChange, lastPageIndex }) => { + if (numberOfPageButtonsToShowAfterAndBeforeCurrent % 2 !== 0) { + throw new Error('number of pages to show must be even!') + } - const [pageIndex, setPageIndex] = useState(0); - const correctedPageIndex = Math.min(pageIndex, lastPageIndex); - const wantedUpperPageIndex = correctedPageIndex + numberOfPageButtonsToShowAfterAndBeforeCurrent; - const wantedLowerPageIndex = correctedPageIndex - numberOfPageButtonsToShowAfterAndBeforeCurrent; + const [pageIndex, setPageIndex] = useState(0) + const correctedPageIndex = Math.min(pageIndex, lastPageIndex) + const wantedUpperPageIndex = correctedPageIndex + numberOfPageButtonsToShowAfterAndBeforeCurrent + const wantedLowerPageIndex = correctedPageIndex - numberOfPageButtonsToShowAfterAndBeforeCurrent - useEffect(() => { - onPageChange(pageIndex) - }, [onPageChange, pageIndex]) + useEffect(() => { + onPageChange(pageIndex) + }, [onPageChange, pageIndex]) - const correctedLowerPageIndex = - Math.min( - Math.max( - Math.min( - wantedLowerPageIndex, - wantedLowerPageIndex + lastPageIndex - wantedUpperPageIndex - ), - 0 - ), - lastPageIndex - ); - - const correctedUpperPageIndex = + const correctedLowerPageIndex = + Math.min( Math.max( - Math.min( - Math.max( - wantedUpperPageIndex, - wantedUpperPageIndex - wantedLowerPageIndex - ), - lastPageIndex - ), - 0 - ); + Math.min( + wantedLowerPageIndex, + wantedLowerPageIndex + lastPageIndex - wantedUpperPageIndex + ), + 0 + ), + lastPageIndex + ) - const paginationItemsBefore = Array.from(new Array(correctedPageIndex - correctedLowerPageIndex)).map((k, index) => { - const itemIndex = correctedLowerPageIndex + index; - return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/> - }); + const correctedUpperPageIndex = + Math.max( + Math.min( + Math.max( + wantedUpperPageIndex, + wantedUpperPageIndex - wantedLowerPageIndex + ), + lastPageIndex + ), + 0 + ) - const paginationItemsAfter = Array.from(new Array(correctedUpperPageIndex - correctedPageIndex)).map((k, index) => { - const itemIndex = correctedPageIndex + index + 1; - return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/> - }); + const paginationItemsBefore = Array.from(new Array(correctedPageIndex - correctedLowerPageIndex)).map((k, index) => { + const itemIndex = correctedLowerPageIndex + index + return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/> + }) - return ( - <Pagination> - { - correctedLowerPageIndex > 0 ? - <Fragment> - <PagerItem key={0} index={0} onClick={setPageIndex}/> - <Pagination.Ellipsis disabled/> - </Fragment> - : null - } - {paginationItemsBefore} - <Pagination.Item active>{correctedPageIndex + 1}</Pagination.Item> - {paginationItemsAfter} - { - correctedUpperPageIndex < lastPageIndex ? - <Fragment> - <Pagination.Ellipsis disabled/> - <PagerItem key={lastPageIndex} index={lastPageIndex} onClick={setPageIndex}/> - </Fragment> - : null - } - </Pagination> - ); -} \ No newline at end of file + const paginationItemsAfter = Array.from(new Array(correctedUpperPageIndex - correctedPageIndex)).map((k, index) => { + const itemIndex = correctedPageIndex + index + 1 + return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/> + }) + + return ( + <Pagination> + { + correctedLowerPageIndex > 0 + ? <Fragment> + <PagerItem key={0} index={0} onClick={setPageIndex}/> + <Pagination.Ellipsis disabled/> + </Fragment> + : null + } + {paginationItemsBefore} + <Pagination.Item active>{correctedPageIndex + 1}</Pagination.Item> + {paginationItemsAfter} + { + correctedUpperPageIndex < lastPageIndex + ? <Fragment> + <Pagination.Ellipsis disabled/> + <PagerItem key={lastPageIndex} index={lastPageIndex} onClick={setPageIndex}/> + </Fragment> + : null + } + </Pagination> + ) +} diff --git a/src/components/pagination/pager.tsx b/src/components/pagination/pager.tsx index 983b8230f..6cca9d58a 100644 --- a/src/components/pagination/pager.tsx +++ b/src/components/pagination/pager.tsx @@ -1,4 +1,4 @@ -import React, {Fragment, useEffect} from "react"; +import React, { Fragment, useEffect } from 'react' export interface PagerPageProps { pageIndex: number @@ -6,19 +6,18 @@ export interface PagerPageProps { onLastPageIndexChange: (lastPageIndex: number) => void } -export const Pager: React.FC<PagerPageProps> = ({children, numberOfElementsPerPage, pageIndex, onLastPageIndexChange}) => { +export const Pager: React.FC<PagerPageProps> = ({ children, numberOfElementsPerPage, pageIndex, onLastPageIndexChange }) => { + useEffect(() => { + const lastPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1 + onLastPageIndexChange(lastPageIndex) + }, [children, numberOfElementsPerPage, onLastPageIndexChange]) - useEffect(() => { - const lastPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1; - onLastPageIndexChange(lastPageIndex) - }, [children, numberOfElementsPerPage, onLastPageIndexChange]) - - return <Fragment> - { - React.Children.toArray(children).filter((value, index) => { - const pageOfElement = Math.floor((index) / numberOfElementsPerPage); - return (pageOfElement === pageIndex); - }) - } - </Fragment> -} \ No newline at end of file + return <Fragment> + { + React.Children.toArray(children).filter((value, index) => { + const pageOfElement = Math.floor((index) / numberOfElementsPerPage) + return (pageOfElement === pageIndex) + }) + } + </Fragment> +} diff --git a/src/components/sort-button/sort-button.tsx b/src/components/sort-button/sort-button.tsx index bafa260de..4a5a2d432 100644 --- a/src/components/sort-button/sort-button.tsx +++ b/src/components/sort-button/sort-button.tsx @@ -1,7 +1,7 @@ -import React from "react"; -import {IconProp} from "@fortawesome/fontawesome-svg-core"; -import {ButtonProps} from "react-bootstrap"; -import {IconButton} from "../icon-button/icon-button"; +import React from 'react' +import { ButtonProps } from 'react-bootstrap' +import { IconProp } from '../../utils/iconProp' +import { IconButton } from '../icon-button/icon-button' export enum SortModeEnum { up = 1, @@ -10,15 +10,15 @@ export enum SortModeEnum { } const getIcon = (direction: SortModeEnum): IconProp => { - switch (direction) { - default: - case SortModeEnum.no: - return "sort"; - case SortModeEnum.up: - return "sort-up"; - case SortModeEnum.down: - return "sort-down"; - } + switch (direction) { + default: + case SortModeEnum.no: + return 'sort' + case SortModeEnum.up: + return 'sort-up' + case SortModeEnum.down: + return 'sort-down' + } } export interface SortButtonProps extends ButtonProps { @@ -27,21 +27,21 @@ export interface SortButtonProps extends ButtonProps { } const toggleDirection = (direction: SortModeEnum) => { - switch (direction) { - case SortModeEnum.no: - return SortModeEnum.up; - case SortModeEnum.up: - return SortModeEnum.down; - default: - case SortModeEnum.down: - return SortModeEnum.no; - } + switch (direction) { + case SortModeEnum.no: + return SortModeEnum.up + case SortModeEnum.up: + return SortModeEnum.down + default: + case SortModeEnum.down: + return SortModeEnum.no + } } -export const SortButton: React.FC<SortButtonProps> = ({children, variant, onChange, direction}) => { - const toggleSort = () => { - onChange(toggleDirection(direction)); - } +export const SortButton: React.FC<SortButtonProps> = ({ children, variant, onChange, direction }) => { + const toggleSort = () => { + onChange(toggleDirection(direction)) + } - return <IconButton onClick={toggleSort} variant={variant} icon={getIcon(direction)}>{children}</IconButton>; + return <IconButton onClick={toggleSort} variant={variant} icon={getIcon(direction)}>{children}</IconButton> } diff --git a/src/index.tsx b/src/index.tsx index edd130d42..4534119f2 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,27 +1,27 @@ import React from 'react' import ReactDOM from 'react-dom' -import {BrowserRouter as Router} from 'react-router-dom' -import * as serviceWorker from './service-worker'; -import {Landing} from "./components/landing/layout"; -import {ApplicationLoader} from "./components/application-loader/application-loader"; -import {Provider} from "react-redux"; -import {store} from "./utils/store"; -import {setUp} from "./initializers"; +import { BrowserRouter as Router } from 'react-router-dom' +import * as serviceWorker from './service-worker' +import { Landing } from './components/landing/layout' +import { ApplicationLoader } from './components/application-loader/application-loader' +import { Provider } from 'react-redux' +import { store } from './utils/store' +import { setUp } from './initializers' -const initTasks = setUp(); +const initTasks = setUp() ReactDOM.render( - <Provider store={store}> - <ApplicationLoader initTasks={initTasks}> - <Router> - <Landing/> - </Router> - </ApplicationLoader> - </Provider> - , document.getElementById('root') -); + <Provider store={store}> + <ApplicationLoader initTasks={initTasks}> + <Router> + <Landing/> + </Router> + </ApplicationLoader> + </Provider> + , document.getElementById('root') +) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); +serviceWorker.unregister() diff --git a/src/initializers/configLoader.ts b/src/initializers/configLoader.ts index b69385a26..e346a8ac1 100644 --- a/src/initializers/configLoader.ts +++ b/src/initializers/configLoader.ts @@ -1,20 +1,20 @@ -import {getBackendConfig, getFrontendConfig} from "../api/config"; -import {setFrontendConfig} from "../redux/frontend-config/methods"; -import {setBackendConfig} from "../redux/backend-config/methods"; -import {getAndSetUser} from "../utils/apiUtils"; +import { getBackendConfig, getFrontendConfig } from '../api/config' +import { setFrontendConfig } from '../redux/frontend-config/methods' +import { setBackendConfig } from '../redux/backend-config/methods' +import { getAndSetUser } from '../utils/apiUtils' -export async function loadAllConfig() { - const frontendConfig = await getFrontendConfig(); - if (!frontendConfig) { - return Promise.reject("Frontend config empty!"); - } - setFrontendConfig(frontendConfig); +export const loadAllConfig: () => Promise<void> = async () => { + const frontendConfig = await getFrontendConfig() + if (!frontendConfig) { + return Promise.reject(new Error('Frontend config empty!')) + } + setFrontendConfig(frontendConfig) - const backendConfig = await getBackendConfig() - if (!backendConfig) { - return Promise.reject("Backend config empty!"); - } - setBackendConfig(backendConfig) + const backendConfig = await getBackendConfig() + if (!backendConfig) { + return Promise.reject(new Error('Backend config empty!')) + } + setBackendConfig(backendConfig) - await getAndSetUser(); -} \ No newline at end of file + await getAndSetUser() +} diff --git a/src/initializers/fontAwesome.ts b/src/initializers/fontAwesome.ts index 58f034c1a..7f6dcfe06 100644 --- a/src/initializers/fontAwesome.ts +++ b/src/initializers/fontAwesome.ts @@ -1,41 +1,41 @@ -import {library} from "@fortawesome/fontawesome-svg-core"; +import { library } from '@fortawesome/fontawesome-svg-core' import { - faAddressCard, - faBolt, - faChartBar, - faClock, - faCloudDownloadAlt, - faComment, - faDownload, - faFileAlt, - faGlobe, - faPlus, - faSignOutAlt, - faSort, - faSortDown, - faSortUp, - faSync, - faThumbtack, - faTimes, - faTrash, - faTv, - faUpload, - faUsers, -} from "@fortawesome/free-solid-svg-icons"; + faAddressCard, + faBolt, + faChartBar, + faClock, + faCloudDownloadAlt, + faComment, + faDownload, + faFileAlt, + faGlobe, + faPlus, + faSignOutAlt, + faSort, + faSortDown, + faSortUp, + faSync, + faThumbtack, + faTimes, + faTrash, + faTv, + faUpload, + faUsers +} from '@fortawesome/free-solid-svg-icons' import { - faDiscourse, - faDropbox, - faFacebook, - faGithub, - faGitlab, - faGoogle, - faMastodon, - faTwitter -} from "@fortawesome/free-brands-svg-icons"; + faDiscourse, + faDropbox, + faFacebook, + faGithub, + faGitlab, + faGoogle, + faMastodon, + faTwitter +} from '@fortawesome/free-brands-svg-icons' -export function setUpFontAwesome() { - library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt, - faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe, - faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook, - faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown) +export const setUpFontAwesome: () => void = () => { + library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt, + faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe, + faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook, + faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown) } diff --git a/src/initializers/i18n.ts b/src/initializers/i18n.ts index ea0916d61..a08b25811 100644 --- a/src/initializers/i18n.ts +++ b/src/initializers/i18n.ts @@ -1,54 +1,52 @@ -import i18n from 'i18next'; -import Backend from 'i18next-http-backend'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import {initReactI18next} from 'react-i18next'; -import moment from "moment"; -import "moment/locale/ar"; -import "moment/locale/ca"; -import "moment/locale/cs"; -import "moment/locale/da"; -import "moment/locale/de"; -import "moment/locale/el"; -import "moment/locale/eo"; -import "moment/locale/es"; -import "moment/locale/fr"; -import "moment/locale/hi"; -import "moment/locale/hr"; -import "moment/locale/id"; -import "moment/locale/it"; -import "moment/locale/ja"; -import "moment/locale/ko"; -import "moment/locale/nl"; -import "moment/locale/pl"; -import "moment/locale/pt"; -import "moment/locale/ru"; -import "moment/locale/sk"; -import "moment/locale/sr"; -import "moment/locale/sv"; -import "moment/locale/tr"; -import "moment/locale/uk"; -import "moment/locale/vi"; -import "moment/locale/zh-cn"; -import "moment/locale/zh-tw"; +import i18n from 'i18next' +import Backend from 'i18next-http-backend' +import LanguageDetector from 'i18next-browser-languagedetector' +import { initReactI18next } from 'react-i18next' +import moment from 'moment' +import 'moment/locale/ar' +import 'moment/locale/ca' +import 'moment/locale/cs' +import 'moment/locale/da' +import 'moment/locale/de' +import 'moment/locale/el' +import 'moment/locale/eo' +import 'moment/locale/es' +import 'moment/locale/fr' +import 'moment/locale/hi' +import 'moment/locale/hr' +import 'moment/locale/id' +import 'moment/locale/it' +import 'moment/locale/ja' +import 'moment/locale/ko' +import 'moment/locale/nl' +import 'moment/locale/pl' +import 'moment/locale/pt' +import 'moment/locale/ru' +import 'moment/locale/sk' +import 'moment/locale/sr' +import 'moment/locale/sv' +import 'moment/locale/tr' +import 'moment/locale/uk' +import 'moment/locale/vi' +import 'moment/locale/zh-cn' +import 'moment/locale/zh-tw' -export async function setUpI18n() { - await i18n - .use(Backend) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - fallbackLng: 'en', - debug: true, - backend: { - loadPath: '/locales/{{lng}}.json', - }, +export const setUpI18n: () => Promise<void> = async () => { + await i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: 'en', + debug: true, + backend: { + loadPath: '/locales/{{lng}}.json' + }, - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - }, - }) + interpolation: { + escapeValue: false // not needed for react as it escapes by default + } + }) - moment.locale(i18n.language); + moment.locale(i18n.language) } - - diff --git a/src/initializers/index.ts b/src/initializers/index.ts index 064acd7e2..f6c91f9e0 100644 --- a/src/initializers/index.ts +++ b/src/initializers/index.ts @@ -1,16 +1,16 @@ -import {setUpFontAwesome} from "./fontAwesome"; -import {setUpI18n} from "./i18n"; -import {loadAllConfig} from "./configLoader"; +import { setUpFontAwesome } from './fontAwesome' +import { setUpI18n } from './i18n' +import { loadAllConfig } from './configLoader' -function customDelay() { - if (!!window.localStorage.getItem("customDelay")) { - return new Promise((resolve => setTimeout(resolve, 5000))); - } else { - return Promise.resolve() - } +const customDelay: () => Promise<void> = async () => { + if (window.localStorage.getItem('customDelay')) { + return new Promise(resolve => setTimeout(resolve, 5000)) + } else { + return Promise.resolve() + } } -export function setUp() { - setUpFontAwesome(); - return [setUpI18n(), loadAllConfig(), customDelay()] -} \ No newline at end of file +export const setUp: () => Promise<void>[] = () => { + setUpFontAwesome() + return [setUpI18n(), loadAllConfig(), customDelay()] +} diff --git a/src/redux/backend-config/methods.ts b/src/redux/backend-config/methods.ts index 1b5b304f3..757a947af 100644 --- a/src/redux/backend-config/methods.ts +++ b/src/redux/backend-config/methods.ts @@ -1,12 +1,12 @@ -import {BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction} from "./types"; -import {store} from "../../utils/store"; +import { BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction } from './types' +import { store } from '../../utils/store' -export const setBackendConfig = (state: BackendConfigState) => { - const action: SetBackendConfigAction = { - type: SET_BACKEND_CONFIG_ACTION_TYPE, - payload: { - state - } - }; - store.dispatch(action) +export const setBackendConfig: (state: BackendConfigState) => void = (state: BackendConfigState) => { + const action: SetBackendConfigAction = { + type: SET_BACKEND_CONFIG_ACTION_TYPE, + payload: { + state + } + } + store.dispatch(action) } diff --git a/src/redux/backend-config/reducers.ts b/src/redux/backend-config/reducers.ts index bab806eaa..463b35487 100644 --- a/src/redux/backend-config/reducers.ts +++ b/src/redux/backend-config/reducers.ts @@ -1,38 +1,38 @@ -import {Reducer} from 'redux'; -import {BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE} from './types'; +import { Reducer } from 'redux' +import { BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE } from './types' export const initialState: BackendConfigState = { - allowAnonymous: true, - authProviders: { - facebook: false, - github: false, - twitter: false, - gitlab: false, - dropbox: false, - ldap: false, - google: false, - saml: false, - oauth2: false, - email: false, - openid: false - }, - customAuthNames: { - ldap: "", - oauth2: "", - saml: "" - }, - specialLinks: { - privacy: "", - termsOfUse: "", - imprint: "", - } -}; + allowAnonymous: true, + authProviders: { + facebook: false, + github: false, + twitter: false, + gitlab: false, + dropbox: false, + ldap: false, + google: false, + saml: false, + oauth2: false, + email: false, + openid: false + }, + customAuthNames: { + ldap: '', + oauth2: '', + saml: '' + }, + specialLinks: { + privacy: '', + termsOfUse: '', + imprint: '' + } +} export const BackendConfigReducer: Reducer<BackendConfigState, BackendConfigActions> = (state: BackendConfigState = initialState, action: BackendConfigActions) => { - switch (action.type) { - case SET_BACKEND_CONFIG_ACTION_TYPE: - return action.payload.state; - default: - return state; - } -}; + switch (action.type) { + case SET_BACKEND_CONFIG_ACTION_TYPE: + return action.payload.state + default: + return state + } +} diff --git a/src/redux/backend-config/types.ts b/src/redux/backend-config/types.ts index 0ada4d3a0..04bd92cbd 100644 --- a/src/redux/backend-config/types.ts +++ b/src/redux/backend-config/types.ts @@ -1,6 +1,6 @@ -import {Action} from "redux"; +import { Action } from 'redux' -export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set'; +export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set' export interface BackendConfigState { allowAnonymous: boolean, diff --git a/src/redux/frontend-config/methods.ts b/src/redux/frontend-config/methods.ts index 4d07612fa..fe4d970d1 100644 --- a/src/redux/frontend-config/methods.ts +++ b/src/redux/frontend-config/methods.ts @@ -1,12 +1,12 @@ -import {FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction} from "./types"; -import {store} from "../../utils/store"; +import { FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction } from './types' +import { store } from '../../utils/store' -export const setFrontendConfig = (state: FrontendConfigState) => { - const action: SetFrontendConfigAction = { - type: SET_FRONTEND_CONFIG_ACTION_TYPE, - payload: { - state - } +export const setFrontendConfig: (state: FrontendConfigState) => void = (state: FrontendConfigState) => { + const action: SetFrontendConfigAction = { + type: SET_FRONTEND_CONFIG_ACTION_TYPE, + payload: { + state } - store.dispatch(action); + } + store.dispatch(action) } diff --git a/src/redux/frontend-config/reducers.ts b/src/redux/frontend-config/reducers.ts index 3f0ca8a55..34975a428 100644 --- a/src/redux/frontend-config/reducers.ts +++ b/src/redux/frontend-config/reducers.ts @@ -1,15 +1,15 @@ -import {Reducer} from 'redux'; -import {FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE} from './types'; +import { Reducer } from 'redux' +import { FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE } from './types' export const initialState: FrontendConfigState = { - backendUrl: "" -}; + backendUrl: '' +} export const FrontendConfigReducer: Reducer<FrontendConfigState, FrontendConfigActions> = (state: FrontendConfigState = initialState, action: FrontendConfigActions) => { - switch (action.type) { - case SET_FRONTEND_CONFIG_ACTION_TYPE: - return action.payload.state; - default: - return state; - } -}; + switch (action.type) { + case SET_FRONTEND_CONFIG_ACTION_TYPE: + return action.payload.state + default: + return state + } +} diff --git a/src/redux/frontend-config/types.ts b/src/redux/frontend-config/types.ts index 06175fd42..69b2321e5 100644 --- a/src/redux/frontend-config/types.ts +++ b/src/redux/frontend-config/types.ts @@ -1,6 +1,6 @@ -import {Action} from "redux"; +import { Action } from 'redux' -export const SET_FRONTEND_CONFIG_ACTION_TYPE = 'frontend-config/set'; +export const SET_FRONTEND_CONFIG_ACTION_TYPE = 'frontend-config/set' export interface SetFrontendConfigAction extends Action { type: string; diff --git a/src/redux/index.ts b/src/redux/index.ts index 4f07b8c06..74e66c4bc 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,12 +1,12 @@ -import {combineReducers, Reducer} from 'redux'; -import {UserState} from "./user/types"; -import {UserReducer} from "./user/reducers"; -import {ModalShowReducer} from "./modal/reducers"; -import {ModalShowState} from "./modal/types"; -import {BackendConfigState} from "./backend-config/types"; -import {FrontendConfigState} from "./frontend-config/types"; -import {BackendConfigReducer} from "./backend-config/reducers"; -import {FrontendConfigReducer} from "./frontend-config/reducers"; +import { combineReducers, Reducer } from 'redux' +import { UserState } from './user/types' +import { UserReducer } from './user/reducers' +import { ModalShowReducer } from './modal/reducers' +import { ModalShowState } from './modal/types' +import { BackendConfigState } from './backend-config/types' +import { FrontendConfigState } from './frontend-config/types' +import { BackendConfigReducer } from './backend-config/reducers' +import { FrontendConfigReducer } from './frontend-config/reducers' export interface ApplicationState { user: UserState; @@ -16,8 +16,8 @@ export interface ApplicationState { } export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({ - user: UserReducer, - modalShow: ModalShowReducer, - backendConfig: BackendConfigReducer, - frontendConfig: FrontendConfigReducer -}); + user: UserReducer, + modalShow: ModalShowReducer, + backendConfig: BackendConfigReducer, + frontendConfig: FrontendConfigReducer +}) diff --git a/src/redux/modal/methods.ts b/src/redux/modal/methods.ts index c9e46a1b8..f9d89244b 100644 --- a/src/redux/modal/methods.ts +++ b/src/redux/modal/methods.ts @@ -1,7 +1,7 @@ -import {ActionCreator} from "redux"; -import {SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, SetHistoryDeleteModalShowAction} from "./types"; +import { ActionCreator } from 'redux' +import { SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, SetHistoryDeleteModalShowAction } from './types' export const setSignInModalShow: ActionCreator<SetHistoryDeleteModalShowAction> = (historyDelete: boolean) => ({ - type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, - payload: historyDelete, + type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, + payload: historyDelete }) diff --git a/src/redux/modal/reducers.ts b/src/redux/modal/reducers.ts index b529f377c..118bfa179 100644 --- a/src/redux/modal/reducers.ts +++ b/src/redux/modal/reducers.ts @@ -1,18 +1,18 @@ -import {Reducer} from 'redux'; -import {ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from './types'; +import { Reducer } from 'redux' +import { ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE } from './types' export const initialState: ModalShowState = { - historyDelete: false -}; + historyDelete: false +} export const ModalShowReducer: Reducer<ModalShowState, ModalShowActions> = (state: ModalShowState = initialState, action: ModalShowActions) => { - switch (action.type) { - case SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE: - return { - ...state, - historyDelete: action.payload - }; - default: - return state; - } -}; + switch (action.type) { + case SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE: + return { + ...state, + historyDelete: action.payload + } + default: + return state + } +} diff --git a/src/redux/modal/types.ts b/src/redux/modal/types.ts index 029ea5cef..17bdab9ae 100644 --- a/src/redux/modal/types.ts +++ b/src/redux/modal/types.ts @@ -1,6 +1,6 @@ -import {Action} from "redux"; +import { Action } from 'redux' -export const SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE = 'modal/history-delete/set'; +export const SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE = 'modal/history-delete/set' export interface ModalShowState { historyDelete: boolean diff --git a/src/redux/user/methods.ts b/src/redux/user/methods.ts index b85cb21b4..ea7e18ee7 100644 --- a/src/redux/user/methods.ts +++ b/src/redux/user/methods.ts @@ -1,20 +1,20 @@ -import {CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState} from "./types"; -import {store} from "../../utils/store"; +import { CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState } from './types' +import { store } from '../../utils/store' -export const setUser = (state: UserState) => { - const action: SetUserAction = { - type: SET_USER_ACTION_TYPE, - payload: { - state - } +export const setUser: (state: UserState) => void = (state: UserState) => { + const action: SetUserAction = { + type: SET_USER_ACTION_TYPE, + payload: { + state } - store.dispatch(action); + } + store.dispatch(action) } -export const clearUser = () => { - const action: ClearUserAction = { - type: CLEAR_USER_ACTION_TYPE, - payload: {}, - } - store.dispatch(action); -} \ No newline at end of file +export const clearUser: () => void = () => { + const action: ClearUserAction = { + type: CLEAR_USER_ACTION_TYPE, + payload: null + } + store.dispatch(action) +} diff --git a/src/redux/user/reducers.ts b/src/redux/user/reducers.ts index 076b20fc3..2084621a2 100644 --- a/src/redux/user/reducers.ts +++ b/src/redux/user/reducers.ts @@ -1,27 +1,20 @@ -import {Reducer} from 'redux'; -import { - CLEAR_USER_ACTION_TYPE, - LoginStatus, - SET_USER_ACTION_TYPE, - SetUserAction, - UserActions, - UserState -} from './types'; +import { Reducer } from 'redux' +import { CLEAR_USER_ACTION_TYPE, LoginStatus, SET_USER_ACTION_TYPE, SetUserAction, UserActions, UserState } from './types' export const initialState: UserState = { - id: "", - name: "", - photo: "", - status: LoginStatus.forbidden -}; + id: '', + name: '', + photo: '', + status: LoginStatus.forbidden +} export const UserReducer: Reducer<UserState, UserActions> = (state: UserState = initialState, action: UserActions) => { - switch (action.type) { - case SET_USER_ACTION_TYPE: - return (action as SetUserAction).payload.state; - case CLEAR_USER_ACTION_TYPE: - return initialState; - default: - return state; - } -}; + switch (action.type) { + case SET_USER_ACTION_TYPE: + return (action as SetUserAction).payload.state + case CLEAR_USER_ACTION_TYPE: + return initialState + default: + return state + } +} diff --git a/src/redux/user/types.ts b/src/redux/user/types.ts index 855d92e3d..51ebed72a 100644 --- a/src/redux/user/types.ts +++ b/src/redux/user/types.ts @@ -1,7 +1,7 @@ -import {Action} from "redux"; +import { Action } from 'redux' -export const SET_USER_ACTION_TYPE = 'user/set'; -export const CLEAR_USER_ACTION_TYPE = 'user/clear'; +export const SET_USER_ACTION_TYPE = 'user/set' +export const CLEAR_USER_ACTION_TYPE = 'user/clear' export interface SetUserAction extends Action { type: string; @@ -12,7 +12,7 @@ export interface SetUserAction extends Action { export interface ClearUserAction extends Action { type: string; - payload: {}; + payload: null; } export interface UserState { @@ -23,8 +23,8 @@ export interface UserState { } export enum LoginStatus { - forbidden = "forbidden", - ok = "ok" + forbidden = 'forbidden', + ok = 'ok' } export type UserActions = SetUserAction | ClearUserAction; diff --git a/src/service-worker.ts b/src/service-worker.ts index b09523f15..861ac5423 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -15,61 +15,50 @@ const isLocalhost = Boolean( // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); + new RegExp(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/).exec(window.location.hostname) +) type Config = { onSuccess?: (registration: ServiceWorkerRegistration) => void; onUpdate?: (registration: ServiceWorkerRegistration) => void; }; -export function register(config?: Config) { +export function register (config?: Config):void { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL( process.env.PUBLIC_URL, window.location.href - ); + ) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; + return } window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); + checkValidServiceWorker(swUrl, config) } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } - }); + }) } } -function registerValidSW(swUrl: string, config?: Config) { +function registerValidSW (swUrl: string, config?: Config) { navigator.serviceWorker .register(swUrl) .then(registration => { registration.onupdatefound = () => { - const installingWorker = registration.installing; + const installingWorker = registration.installing if (installingWorker == null) { - return; + return } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { @@ -80,70 +69,70 @@ function registerValidSW(swUrl: string, config?: Config) { console.log( 'New content is available and will be used when all ' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + ) // Execute callback if (config && config.onUpdate) { - config.onUpdate(registration); + config.onUpdate(registration) } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + console.log('Content is cached for offline use.') // Execute callback if (config && config.onSuccess) { - config.onSuccess(registration); + config.onSuccess(registration) } } } - }; - }; + } + } }) .catch(error => { - console.error('Error during service worker registration:', error); - }); + console.error('Error during service worker registration:', error) + }) } -function checkValidServiceWorker(swUrl: string, config?: Config) { +function checkValidServiceWorker (swUrl: string, config?: Config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { headers: { 'Service-Worker': 'script' } }) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); + const contentType = response.headers.get('content-type') if ( response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); + return registration.unregister().then(() => { + window.location.reload() + }) + }).catch(() => console.log('Service worker not ready')) } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } }) .catch(() => { console.log( 'No internet connection found. App is running in offline mode.' - ); - }); + ) + }) } -export function unregister() { +export function unregister ():void { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready .then(registration => { - registration.unregister(); + return registration.unregister() + }) + .catch((error:Error) => { + console.error(error.message) }) - .catch(error => { - console.error(error.message); - }); } } diff --git a/src/setup-tests.ts b/src/setup-tests.ts index 74b1a275a..2eb59b05d 100644 --- a/src/setup-tests.ts +++ b/src/setup-tests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom/extend-expect' diff --git a/src/utils/apiUtils.ts b/src/utils/apiUtils.ts index c828eba74..1933bb237 100644 --- a/src/utils/apiUtils.ts +++ b/src/utils/apiUtils.ts @@ -1,27 +1,22 @@ -import {getMe} from "../api/user"; -import {LoginStatus} from "../redux/user/types"; -import {setUser} from "../redux/user/methods"; -import {store} from "./store"; +import { getMe } from '../api/user' +import { LoginStatus } from '../redux/user/types' +import { setUser } from '../redux/user/methods' +import { store } from './store' -export const getAndSetUser = async () => { - const meResponse = await getMe(); - expectResponseCode(meResponse); - const me = await meResponse.json(); - if (!me) { - return; - } - setUser({ - status: LoginStatus.ok, - id: me.id, - name: me.name, - photo: me.photo, - }); +export const getAndSetUser: () => (Promise<void>) = async () => { + const me = await getMe() + setUser({ + status: LoginStatus.ok, + id: me.id, + name: me.name, + photo: me.photo + }) } -export const getBackendUrl = () => { - return store.getState().frontendConfig.backendUrl; +export const getBackendUrl: (() => string) = () => { + return store.getState().frontendConfig.backendUrl } -export const expectResponseCode = (response: Response, code: number = 200) => { - return (response.status !== code); -} \ No newline at end of file +export const expectResponseCode: ((response: Response, code?: number) => boolean) = (response, code = 200) => { + return response.status !== code +} diff --git a/src/utils/historyUtils.ts b/src/utils/historyUtils.ts index b360a6774..079f9f927 100644 --- a/src/utils/historyUtils.ts +++ b/src/utils/historyUtils.ts @@ -1,61 +1,61 @@ -import {HistoryEntry} from "../components/landing/pages/history/history"; -import moment from "moment"; -import {HistoryToolbarState} from "../components/landing/pages/history/history-toolbar/history-toolbar"; -import {SortModeEnum} from "../components/sort-button/sort-button"; +import { HistoryEntry } from '../components/landing/pages/history/history' +import moment from 'moment' +import { HistoryToolbarState } from '../components/landing/pages/history/history-toolbar/history-toolbar' +import { SortModeEnum } from '../components/sort-button/sort-button' -export function sortAndFilterEntries(entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { - return sortEntries(filterByKeywordSearch(filterBySelectedTags(entries, viewState.selectedTags), viewState.keywordSearch), viewState); +export function sortAndFilterEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { + return sortEntries(filterByKeywordSearch(filterBySelectedTags(entries, viewState.selectedTags), viewState.keywordSearch), viewState) } -function filterBySelectedTags(entries: HistoryEntry[], selectedTags: string[]): HistoryEntry[] { - return entries.filter(entry => { - return (selectedTags.length === 0 || arrayCommonCheck(entry.tags, selectedTags)) - } +function filterBySelectedTags (entries: HistoryEntry[], selectedTags: string[]): HistoryEntry[] { + return entries.filter(entry => { + return (selectedTags.length === 0 || arrayCommonCheck(entry.tags, selectedTags)) + } + ) +} + +function arrayCommonCheck<T> (array1: T[], array2: T[]): boolean { + const foundElement = array1.find((element1) => + array2.find((element2) => + element2 === element1 ) + ) + return !!foundElement } -function arrayCommonCheck<T>(array1: T[], array2: T[]): boolean { - const foundElement = array1.find((element1) => - array2.find((element2) => - element2 === element1 - ) - ) - return !!foundElement; +function filterByKeywordSearch (entries: HistoryEntry[], keywords: string): HistoryEntry[] { + const searchTerm = keywords.toLowerCase() + return entries.filter(entry => entry.title.toLowerCase().indexOf(searchTerm) !== -1) } -function filterByKeywordSearch(entries: HistoryEntry[], keywords: string): HistoryEntry[] { - const searchTerm = keywords.toLowerCase(); - return entries.filter(entry => entry.title.toLowerCase().indexOf(searchTerm) !== -1); +function sortEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { + return entries.sort((firstEntry, secondEntry) => { + if (firstEntry.pinned && !secondEntry.pinned) { + return -1 + } + if (!firstEntry.pinned && secondEntry.pinned) { + return 1 + } + + if (viewState.titleSortDirection !== SortModeEnum.no) { + return firstEntry.title.localeCompare(secondEntry.title) * viewState.titleSortDirection + } + + if (viewState.lastVisitedSortDirection !== SortModeEnum.no) { + if (firstEntry.lastVisited > secondEntry.lastVisited) { + return 1 * viewState.lastVisitedSortDirection + } + if (firstEntry.lastVisited < secondEntry.lastVisited) { + return -1 * viewState.lastVisitedSortDirection + } + } + + return 0 + }) } -function sortEntries(entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] { - return entries.sort((firstEntry, secondEntry) => { - if (firstEntry.pinned && !secondEntry.pinned) { - return -1; - } - if (!firstEntry.pinned && secondEntry.pinned) { - return 1; - } - - if (viewState.titleSortDirection !== SortModeEnum.no) { - return firstEntry.title.localeCompare(secondEntry.title) * viewState.titleSortDirection; - } - - if (viewState.lastVisitedSortDirection !== SortModeEnum.no) { - if (firstEntry.lastVisited > secondEntry.lastVisited) { - return 1 * viewState.lastVisitedSortDirection; - } - if (firstEntry.lastVisited < secondEntry.lastVisited) { - return -1 * viewState.lastVisitedSortDirection; - } - } - - return 0; - }) -} - -export function formatHistoryDate(date: Date) { - return moment(date).format("llll") +export function formatHistoryDate (date: Date): string { + return moment(date).format('llll') } export interface OldHistoryEntry { @@ -66,23 +66,23 @@ export interface OldHistoryEntry { pinned: boolean; } -export function loadHistoryFromLocalStore(): HistoryEntry[] { - const historyJsonString = window.localStorage.getItem("history"); - if (!historyJsonString) { - // if localStorage["history"] is empty we check the old localStorage["notehistory"] - // and convert it to the new format - const oldHistoryJsonString = window.localStorage.getItem("notehistory") - const oldHistory = !!oldHistoryJsonString ? JSON.parse(JSON.parse(oldHistoryJsonString)) : []; - return oldHistory.map((entry: OldHistoryEntry) => { - return { - id: entry.id, - title: entry.text, - lastVisited: moment(entry.time).toDate(), - tags: entry.tags, - pinned: entry.pinned, - } - }) - } else { - return JSON.parse(historyJsonString) - } -} \ No newline at end of file +export function loadHistoryFromLocalStore (): HistoryEntry[] { + const historyJsonString = window.localStorage.getItem('history') + if (!historyJsonString) { + // if localStorage["history"] is empty we check the old localStorage["notehistory"] + // and convert it to the new format + const oldHistoryJsonString = window.localStorage.getItem('notehistory') + const oldHistory = oldHistoryJsonString ? JSON.parse(JSON.parse(oldHistoryJsonString)) as OldHistoryEntry[] : [] + return oldHistory.map((entry: OldHistoryEntry) => { + return { + id: entry.id, + title: entry.text, + lastVisited: moment(entry.time).toDate(), + tags: entry.tags, + pinned: entry.pinned + } + }) + } else { + return JSON.parse(historyJsonString) as HistoryEntry[] + } +} diff --git a/src/utils/iconProp.ts b/src/utils/iconProp.ts new file mode 100644 index 000000000..a03b5de72 --- /dev/null +++ b/src/utils/iconProp.ts @@ -0,0 +1,4 @@ +import { IconLookup, IconName, IconPrefix } from '@fortawesome/fontawesome-common-types' + +// This icon prop is a workaround, because ESLint doesn't find the font awesome IconProp +export type IconProp = IconName | [IconPrefix, IconName] | IconLookup diff --git a/src/utils/store.ts b/src/utils/store.ts index 375d2d397..8b19d3391 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,4 +1,4 @@ -import {createStore} from "redux"; -import {allReducers} from "../redux"; +import { createStore } from 'redux' +import { allReducers } from '../redux' -export const store = createStore(allReducers); +export const store = createStore(allReducers) diff --git a/yarn.lock b/yarn.lock index 15a21c892..1cee6fb57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1728,6 +1728,17 @@ regexpp "^3.0.0" tsutils "^3.17.1" +"@typescript-eslint/eslint-plugin@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.0.tgz#02f8ec6b5ce814bda80dfc22463f108bed1f699b" + integrity sha512-lcZ0M6jD4cqGccYOERKdMtg+VWpoq3NSnWVxpc/AwAy0zhkUYVioOUZmfNqiNH8/eBNGhCn6HXd6mKIGRgNc1Q== + dependencies: + "@typescript-eslint/experimental-utils" "3.0.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + "@typescript-eslint/experimental-utils@2.31.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.31.0.tgz#a9ec514bf7fd5e5e82bc10dcb6a86d58baae9508" @@ -1738,6 +1749,16 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" +"@typescript-eslint/experimental-utils@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.0.tgz#1ddf53eeb61ac8eaa9a77072722790ac4f641c03" + integrity sha512-BN0vmr9N79M9s2ctITtChRuP1+Dls0x/wlg0RXW1yQ7WJKPurg6X3Xirv61J2sjPif4F8SLsFMs5Nzte0WYoTQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + "@typescript-eslint/parser@^2.10.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.31.0.tgz#beddd4e8efe64995108b229b2862cd5752d40d6f" @@ -1748,6 +1769,16 @@ "@typescript-eslint/typescript-estree" "2.31.0" eslint-visitor-keys "^1.1.0" +"@typescript-eslint/parser@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.0.0.tgz#fe9fdf18a1155c02c04220c14506a320cb6c6944" + integrity sha512-8RRCA9KLxoFNO0mQlrLZA0reGPd/MsobxZS/yPFj+0/XgMdS8+mO8mF3BDj2ZYQj03rkayhSJtF1HAohQ3iylw== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "3.0.0" + "@typescript-eslint/typescript-estree" "3.0.0" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/typescript-estree@2.31.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.31.0.tgz#ac536c2d46672aa1f27ba0ec2140d53670635cfd" @@ -1761,6 +1792,19 @@ semver "^6.3.0" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.0.tgz#fa40e1b76ccff880130be054d9c398e96004bf42" + integrity sha512-nevQvHyNghsfLrrByzVIH4ZG3NROgJ8LZlfh3ddwPPH4CH7W4GAiSx5qu+xHuX5pWsq6q/eqMc1io840ZhAnUg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -4223,6 +4267,11 @@ eslint-config-react-app@^5.2.1: dependencies: confusing-browser-globals "^1.0.9" +eslint-config-standard@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + eslint-import-resolver-node@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" @@ -4250,6 +4299,14 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + eslint-plugin-flowtype@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451" @@ -4257,6 +4314,14 @@ eslint-plugin-flowtype@4.6.0: dependencies: lodash "^4.17.15" +eslint-plugin-flowtype@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.1.0.tgz#0209f06e68b1cdc8f2bd44034753379aafcddb76" + integrity sha512-avZ1nHs0vadDTPvgGbggLWvktqI7urjZ1fcK8P+AXJkTuOSBmNje/vMtbfXgs85d32nMYioD7LoLNZiEULZ8lA== + dependencies: + lodash "^4.17.15" + string-natural-compare "^3.0.1" + eslint-plugin-import@2.20.1: version "2.20.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" @@ -4275,7 +4340,25 @@ eslint-plugin-import@2.20.1: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-jsx-a11y@6.2.3: +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + +eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@^6.2.3: version "6.2.3" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg== @@ -4290,6 +4373,23 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" @@ -4313,6 +4413,11 @@ eslint-plugin-react@7.19.0: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" +eslint-plugin-standard@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" + integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -5523,6 +5628,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.6.tgz#643194ad4bf2712f37852e386b6998eff0db2106" + integrity sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA== + immer@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" @@ -9752,7 +9862,7 @@ resolve@1.15.0: dependencies: path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -9978,7 +10088,7 @@ selfsigned@^1.10.7: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@6.3.0, semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -9988,6 +10098,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -10460,6 +10575,11 @@ string-length@^3.1.0: astral-regex "^1.0.0" strip-ansi "^5.2.0" +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"