mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
better linting (#72)
Improve linting and fix linting errors Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
efb6513205
commit
eba59ae622
70 changed files with 2413 additions and 1867 deletions
437
.editorconfig
Normal file
437
.editorconfig
Normal file
|
@ -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
|
||||||
|
|
27
.github/workflows/build.yml
vendored
Normal file
27
.github/workflows/build.yml
vendored
Normal file
|
@ -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
|
30
package.json
30
package.json
|
@ -47,7 +47,25 @@
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"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": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
@ -64,6 +82,16 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/redux-devtools": "^3.0.47",
|
"@types/redux-devtools": "^3.0.47",
|
||||||
"@types/redux-devtools-extension": "^2.13.2",
|
"@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": "^3.5.0",
|
||||||
"redux-devtools-extension": "^2.13.8"
|
"redux-devtools-extension": "^2.13.8"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import {FrontendConfigState} from "../redux/frontend-config/types";
|
import { FrontendConfigState } from '../redux/frontend-config/types'
|
||||||
import {BackendConfigState} from "../redux/backend-config/types";
|
import { BackendConfigState } from '../redux/backend-config/types'
|
||||||
import {expectResponseCode, getBackendUrl} from "../utils/apiUtils";
|
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
||||||
|
|
||||||
export const getBackendConfig = async () => {
|
export const getBackendConfig: () => Promise<BackendConfigState> = async () => {
|
||||||
const response = await fetch(getBackendUrl() + '/backend-config.json');
|
const response = await fetch(getBackendUrl() + '/backend-config.json')
|
||||||
expectResponseCode(response);
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<BackendConfigState>;
|
return await response.json() as Promise<BackendConfigState>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendConfig = async () => {
|
export const getFrontendConfig: () => Promise<FrontendConfigState> = async () => {
|
||||||
const response = await fetch('config.json');
|
const response = await fetch('config.json')
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<FrontendConfigState>;
|
return await response.json() as Promise<FrontendConfigState>
|
||||||
}
|
}
|
115
src/api/user.ts
115
src/api/user.ts
|
@ -1,67 +1,72 @@
|
||||||
import {expectResponseCode, getBackendUrl} from "../utils/apiUtils";
|
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
||||||
|
|
||||||
export const getMe = async () => {
|
export const getMe: (() => Promise<meResponse>) = async () => {
|
||||||
return fetch('/me');
|
const response = await fetch('/me')
|
||||||
|
expectResponseCode(response)
|
||||||
|
return (await response.json()) as meResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postEmailLogin = async (email: string, password: string) => {
|
export interface meResponse {
|
||||||
const response = await fetch(getBackendUrl() + "/auth/email", {
|
id: string
|
||||||
method: 'POST',
|
name: string
|
||||||
mode: 'cors',
|
photo: string
|
||||||
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 const postLdapLogin = async (username: string, password: string) => {
|
export const postEmailLogin: ((email: string, password: string) => Promise<void>) = async (email, password) => {
|
||||||
const response = await fetch(getBackendUrl() + "/auth/ldap", {
|
const response = await fetch(getBackendUrl() + '/auth/email', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
referrerPolicy: 'no-referrer',
|
referrerPolicy: 'no-referrer',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: username,
|
email: email,
|
||||||
password: password,
|
password: password
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postOpenIdLogin = async (openId: string) => {
|
export const postLdapLogin: ((email: string, password: string) => Promise<void>) = async (username, password) => {
|
||||||
const response = await fetch(getBackendUrl() + "/auth/openid", {
|
const response = await fetch(getBackendUrl() + '/auth/ldap', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
referrerPolicy: 'no-referrer',
|
referrerPolicy: 'no-referrer',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
openId: openId
|
username: username,
|
||||||
})
|
password: password
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json();
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
import React, {Fragment, useEffect, useState} from "react";
|
import React, { Fragment, useEffect, useState } from 'react'
|
||||||
import "./application-loader.scss";
|
import './application-loader.scss'
|
||||||
import {LoadingScreen} from "./loading-screen";
|
import { LoadingScreen } from './loading-screen'
|
||||||
|
|
||||||
interface ApplicationLoaderProps {
|
interface ApplicationLoaderProps {
|
||||||
initTasks: Promise<any>[]
|
initTasks: Promise<void>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({children, initTasks}) => {
|
export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({ children, initTasks }) => {
|
||||||
const [failed, setFailed] = useState<boolean>(false);
|
const [failed, setFailed] = useState<boolean>(false)
|
||||||
const [doneTasks, setDoneTasks] = useState<number>(0);
|
const [doneTasks, setDoneTasks] = useState<number>(0)
|
||||||
|
|
||||||
useEffect(() => {
|
const runTask:((task: Promise<void>) => (Promise<void>)) = async (task) => {
|
||||||
setDoneTasks(0);
|
await task
|
||||||
initTasks.forEach(task => {
|
setDoneTasks(prevDoneTasks => {
|
||||||
(async () => {
|
return prevDoneTasks + 1
|
||||||
try {
|
})
|
||||||
await task;
|
}
|
||||||
setDoneTasks(prevDoneTasks => {
|
|
||||||
return prevDoneTasks + 1;
|
|
||||||
})
|
|
||||||
} catch (reason) {
|
|
||||||
setFailed(true);
|
|
||||||
console.error(reason);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
})
|
|
||||||
}, [initTasks]);
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
doneTasks < initTasks.length || initTasks.length === 0 ?
|
setDoneTasks(0)
|
||||||
<LoadingScreen failed={failed}/> :
|
for (const task of initTasks) {
|
||||||
<Fragment>{children}</Fragment>
|
runTask(task).catch(reason => {
|
||||||
);
|
setFailed(true)
|
||||||
|
console.error(reason)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [initTasks])
|
||||||
|
|
||||||
|
return (
|
||||||
|
doneTasks < initTasks.length || initTasks.length === 0
|
||||||
|
? <LoadingScreen failed={failed}/>
|
||||||
|
: <Fragment>{children}</Fragment>
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {Alert} from "react-bootstrap";
|
import { Alert } from 'react-bootstrap'
|
||||||
|
|
||||||
export interface LoadingScreenProps {
|
export interface LoadingScreenProps {
|
||||||
failed: boolean
|
failed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadingScreen: React.FC<LoadingScreenProps> = ({failed}) => {
|
export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failed }) => {
|
||||||
return (
|
return (
|
||||||
<div className="loader middle">
|
<div className="loader middle">
|
||||||
<div className="icon">
|
<div className="icon">
|
||||||
<FontAwesomeIcon icon="file-alt" size="6x"
|
<FontAwesomeIcon icon="file-alt" size="6x"
|
||||||
className={failed ? "animation-shake" : "animation-pulse"}/>
|
className={failed ? 'animation-shake' : 'animation-pulse'}/>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
failed ? <Alert variant={"danger"}>An error occured while loading the application!</Alert> : null
|
failed ? <Alert variant={'danger'}>An error occurred while loading the application!</Alert> : null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,27 +1,27 @@
|
||||||
import React, {Fragment} from "react";
|
import React, { Fragment } from 'react'
|
||||||
|
|
||||||
export interface ElementSeparatorProps {
|
export interface ElementSeparatorProps {
|
||||||
separator: React.ReactElement
|
separator: React.ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ElementSeparator: React.FC<ElementSeparatorProps> = ({children, separator}) => {
|
export const ElementSeparator: React.FC<ElementSeparatorProps> = ({ children, separator }) => {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{
|
{
|
||||||
React.Children
|
React.Children
|
||||||
.toArray(children)
|
.toArray(children)
|
||||||
.filter(child => child !== null)
|
.filter(child => child !== null)
|
||||||
.map((child, index) => {
|
.map((child, index) => {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{
|
{
|
||||||
(index > 0 ) ? separator : null
|
(index > 0) ? separator : null
|
||||||
}
|
}
|
||||||
{child}
|
{child}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import "./icon-button.scss";
|
import './icon-button.scss'
|
||||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
import { Button, ButtonProps } from 'react-bootstrap'
|
||||||
import {Button, ButtonProps} from "react-bootstrap";
|
import { IconProp } from '../../utils/iconProp'
|
||||||
|
|
||||||
export interface SocialButtonProps extends ButtonProps {
|
export interface SocialButtonProps extends ButtonProps {
|
||||||
icon: IconProp
|
icon: IconProp
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton: React.FC<SocialButtonProps> = ({icon, children, variant, onClick}) => {
|
export const IconButton: React.FC<SocialButtonProps> = ({ icon, children, variant, onClick }) => {
|
||||||
return (
|
return (
|
||||||
<Button variant={variant} className={"btn-icon p-0 d-inline-flex align-items-stretch"}
|
<Button variant={variant} className={'btn-icon p-0 d-inline-flex align-items-stretch'}
|
||||||
onClick={() => onClick?.()}>
|
onClick={() => onClick?.()}>
|
||||||
<span className="icon-part d-flex align-items-center">
|
<span className="icon-part d-flex align-items-center">
|
||||||
<FontAwesomeIcon icon={icon} className={"icon"}/>
|
<FontAwesomeIcon icon={icon} className={'icon'}/>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-part d-flex align-items-center">
|
<span className="text-part d-flex align-items-center">
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
import React, {Fragment} from "react";
|
import React, { Fragment } from 'react'
|
||||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { IconProp } from '../../../../utils/iconProp'
|
||||||
|
|
||||||
export interface ExternalLinkProp {
|
export interface ExternalLinkProp {
|
||||||
href: string;
|
href: string;
|
||||||
text: string;
|
text: string;
|
||||||
icon?: IconProp;
|
icon?: IconProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalLink: React.FC<ExternalLinkProp> = ({href, text, icon}) => {
|
export const ExternalLink: React.FC<ExternalLinkProp> = ({ href, text, icon }) => {
|
||||||
return (
|
return (
|
||||||
<a href={href}
|
<a href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-light">
|
className="text-light">
|
||||||
{
|
{
|
||||||
icon ?
|
icon
|
||||||
<Fragment>
|
? <Fragment>
|
||||||
<FontAwesomeIcon icon={icon}/>
|
<FontAwesomeIcon icon={icon}/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
:
|
: null
|
||||||
null
|
}
|
||||||
}
|
{text}
|
||||||
{text}
|
</a>
|
||||||
</a>
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {LanguagePicker} from "./language-picker";
|
import { LanguagePicker } from './language-picker'
|
||||||
import {PoweredByLinks} from "./powered-by-links";
|
import { PoweredByLinks } from './powered-by-links'
|
||||||
import {SocialLink} from "./social-links";
|
import { SocialLink } from './social-links'
|
||||||
|
|
||||||
export const Footer: React.FC = () => {
|
export const Footer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="text-white-50 small">
|
<footer className="text-white-50 small">
|
||||||
<LanguagePicker/>
|
<LanguagePicker/>
|
||||||
<PoweredByLinks/>
|
<PoweredByLinks/>
|
||||||
<SocialLink/>
|
<SocialLink/>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {useTranslation} from "react-i18next";
|
import { useTranslation } from 'react-i18next'
|
||||||
import moment from "moment";
|
import moment from 'moment'
|
||||||
import { Form } from "react-bootstrap";
|
import { Form } from 'react-bootstrap'
|
||||||
|
|
||||||
const LanguagePicker: React.FC = () => {
|
const LanguagePicker: React.FC = () => {
|
||||||
const {i18n} = useTranslation();
|
const { i18n } = useTranslation()
|
||||||
|
|
||||||
const onChangeLang = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const onChangeLang = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
moment.locale(event.currentTarget.value);
|
moment.locale(event.currentTarget.value)
|
||||||
i18n.changeLanguage(event.currentTarget.value);
|
await i18n.changeLanguage(event.currentTarget.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="mb-2 mx-auto w-auto"
|
className="mb-2 mx-auto w-auto"
|
||||||
value={i18n.language}
|
value={i18n.language}
|
||||||
onChange={onChangeLang}
|
onChange={onChangeLang}
|
||||||
>
|
>
|
||||||
<option value="en">English</option>
|
<option value="en">English</option>
|
||||||
<option value="zh-CN">简体中文</option>
|
<option value="zh-CN">简体中文</option>
|
||||||
<option value="zh-TW">繁體中文</option>
|
<option value="zh-TW">繁體中文</option>
|
||||||
<option value="fr">Français</option>
|
<option value="fr">Français</option>
|
||||||
<option value="de">Deutsch</option>
|
<option value="de">Deutsch</option>
|
||||||
<option value="ja">日本語</option>
|
<option value="ja">日本語</option>
|
||||||
<option value="es">Español</option>
|
<option value="es">Español</option>
|
||||||
<option value="ca">Català</option>
|
<option value="ca">Català</option>
|
||||||
<option value="el">Ελληνικά</option>
|
<option value="el">Ελληνικά</option>
|
||||||
<option value="pt">Português</option>
|
<option value="pt">Português</option>
|
||||||
<option value="it">Italiano</option>
|
<option value="it">Italiano</option>
|
||||||
<option value="tr">Türkçe</option>
|
<option value="tr">Türkçe</option>
|
||||||
<option value="ru">Русский</option>
|
<option value="ru">Русский</option>
|
||||||
<option value="nl">Nederlands</option>
|
<option value="nl">Nederlands</option>
|
||||||
<option value="hr">Hrvatski</option>
|
<option value="hr">Hrvatski</option>
|
||||||
<option value="pl">Polski</option>
|
<option value="pl">Polski</option>
|
||||||
<option value="uk">Українська</option>
|
<option value="uk">Українська</option>
|
||||||
<option value="hi">हिन्दी</option>
|
<option value="hi">हिन्दी</option>
|
||||||
<option value="sv">Svenska</option>
|
<option value="sv">Svenska</option>
|
||||||
<option value="eo">Esperanto</option>
|
<option value="eo">Esperanto</option>
|
||||||
<option value="da">Dansk</option>
|
<option value="da">Dansk</option>
|
||||||
<option value="ko">한국어</option>
|
<option value="ko">한국어</option>
|
||||||
<option value="id">Bahasa Indonesia</option>
|
<option value="id">Bahasa Indonesia</option>
|
||||||
<option value="sr">Cрпски</option>
|
<option value="sr">Cрпски</option>
|
||||||
<option value="vi">Tiếng Việt</option>
|
<option value="vi">Tiếng Việt</option>
|
||||||
<option value="ar">العربية</option>
|
<option value="ar">العربية</option>
|
||||||
<option value="cs">Česky</option>
|
<option value="cs">Česky</option>
|
||||||
<option value="sk">Slovensky</option>
|
<option value="sk">Slovensky</option>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { LanguagePicker }
|
export { LanguagePicker }
|
||||||
|
|
|
@ -1,51 +1,35 @@
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {TranslatedLink} from "./translated-link";
|
import { TranslatedLink } from './translated-link'
|
||||||
import React, {Fragment} from "react";
|
import React, { Fragment } from 'react'
|
||||||
import {ExternalLink} from "./external-link";
|
import { ExternalLink } from './external-link'
|
||||||
import {useSelector} from "react-redux";
|
import { useSelector } from 'react-redux'
|
||||||
import {ApplicationState} from "../../../../redux";
|
import { ApplicationState } from '../../../../redux'
|
||||||
|
|
||||||
const PoweredByLinks: React.FC = () => {
|
export const PoweredByLinks: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
const defaultLinks = [
|
|
||||||
{
|
|
||||||
href: '/s/release-notes',
|
|
||||||
i18nKey: 'releases'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://github.com/codimd/server/tree/41b13e71b6b1d499238c04b15d65e3bd76442f1d',
|
|
||||||
i18nKey: 'sourceCode'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
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)
|
const config = useSelector((state: ApplicationState) => state.backendConfig)
|
||||||
.filter(([_, value]) => value !== "")
|
|
||||||
.map(([key, value]) => {
|
|
||||||
return {
|
|
||||||
href: value,
|
|
||||||
i18nKey: key
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
|
<Trans i18nKey="poweredBy" components={[<ExternalLink href="https://codimd.org" text="CodiMD"/>]}/>
|
||||||
|
{
|
||||||
{
|
Object.entries({ ...defaultLinks, ...(config.specialLinks) }).map(([i18nKey, href]) =>
|
||||||
(defaultLinks.concat(specialLinks)).map(({href, i18nKey}) =>
|
<Fragment key={i18nKey}>
|
||||||
<Fragment key={i18nKey}>
|
|
|
||||||
|
|
<TranslatedLink
|
||||||
<TranslatedLink
|
href={href}
|
||||||
href={href}
|
i18nKey={i18nKey}
|
||||||
i18nKey={i18nKey}
|
/>
|
||||||
/>
|
</Fragment>
|
||||||
</Fragment>
|
)
|
||||||
)
|
}
|
||||||
}
|
</p>
|
||||||
</p>
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { PoweredByLinks }
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {ExternalLink} from "./external-link";
|
import { ExternalLink } from './external-link'
|
||||||
|
|
||||||
const SocialLink: React.FC = () => {
|
const SocialLink: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<Trans i18nKey="followUs" components={[
|
<Trans i18nKey="followUs" components={[
|
||||||
<ExternalLink href="https://github.com/codimd/server" icon={['fab', 'github']} text="GitHub"/>,
|
<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://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://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://social.codimd.org/mastodon" icon={['fab', 'mastodon']} text="Mastodon"/>,
|
||||||
<ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/>
|
<ExternalLink href="https://translate.codimd.org" icon="globe" text="POEditor"/>
|
||||||
]}/>
|
]}/>
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { SocialLink }
|
export { SocialLink }
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export interface TranslatedLinkProps {
|
export interface TranslatedLinkProps {
|
||||||
href: string;
|
href: string;
|
||||||
i18nKey: string;
|
i18nKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TranslatedLink: React.FC<TranslatedLinkProps> = ({href, i18nKey}) => {
|
const TranslatedLink: React.FC<TranslatedLinkProps> = ({ href, i18nKey }) => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<a href={href}
|
<a href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-light">
|
className="text-light">
|
||||||
<Trans i18nKey={i18nKey}/>
|
<Trans i18nKey={i18nKey}/>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {TranslatedLink}
|
export { TranslatedLink }
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Redirect, Route, Switch} from "react-router-dom";
|
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||||
import {History} from "../pages/history/history";
|
import { History } from '../pages/history/history'
|
||||||
import {Intro} from "../pages/intro/intro";
|
import { Intro } from '../pages/intro/intro'
|
||||||
import {Container} from "react-bootstrap";
|
import { Container } from 'react-bootstrap'
|
||||||
import {HeaderBar} from "./navigation/header-bar/header-bar";
|
import { HeaderBar } from './navigation/header-bar/header-bar'
|
||||||
import {Footer} from "./footer/footer";
|
import { Footer } from './footer/footer'
|
||||||
import "./style/index.scss";
|
import './style/index.scss'
|
||||||
import {Login} from "../pages/login/login";
|
import { Login } from '../pages/login/login'
|
||||||
|
|
||||||
export const Landing: React.FC = () => {
|
export const Landing: React.FC = () => {
|
||||||
return (<Container>
|
return (<Container>
|
||||||
<HeaderBar/>
|
<HeaderBar/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/history">
|
<Route path="/history">
|
||||||
<History/>
|
<History/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/intro">
|
<Route path="/intro">
|
||||||
<Intro/>
|
<Intro/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/login">
|
<Route path="/login">
|
||||||
<Login/>
|
<Login/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Redirect to="/intro"/>
|
<Redirect to="/intro"/>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Footer/>
|
<Footer/>
|
||||||
</Container>)
|
</Container>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,48 @@
|
||||||
import React, {Fragment} from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import {Navbar} from 'react-bootstrap';
|
import { Navbar } from 'react-bootstrap'
|
||||||
import {useSelector} from "react-redux";
|
import { useSelector } from 'react-redux'
|
||||||
import {ApplicationState} from "../../../../../redux";
|
import { ApplicationState } from '../../../../../redux'
|
||||||
import {NewUserNoteButton} from "../new-user-note-button";
|
import { NewUserNoteButton } from '../new-user-note-button'
|
||||||
import {UserDropdown} from "../user-dropdown/user-dropdown";
|
import { UserDropdown } from '../user-dropdown/user-dropdown'
|
||||||
import {SignInButton} from "../sign-in-button";
|
import { SignInButton } from '../sign-in-button'
|
||||||
import {NewGuestNoteButton} from "../new-guest-note-button";
|
import { NewGuestNoteButton } from '../new-guest-note-button'
|
||||||
import {LoginStatus} from "../../../../../redux/user/types";
|
import { LoginStatus } from '../../../../../redux/user/types'
|
||||||
import {HeaderNavLink} from "../header-nav-link";
|
import { HeaderNavLink } from '../header-nav-link'
|
||||||
import "./header-bar.scss";
|
import './header-bar.scss'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const HeaderBar: React.FC = () => {
|
const HeaderBar: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const user = useSelector((state: ApplicationState) => state.user);
|
const user = useSelector((state: ApplicationState) => state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className="justify-content-between">
|
<Navbar className="justify-content-between">
|
||||||
<div className="nav header-nav">
|
<div className="nav header-nav">
|
||||||
<HeaderNavLink to="/intro">
|
<HeaderNavLink to="/intro">
|
||||||
<Trans i18nKey="intro"/>
|
<Trans i18nKey="intro"/>
|
||||||
</HeaderNavLink>
|
</HeaderNavLink>
|
||||||
<HeaderNavLink to="/history">
|
<HeaderNavLink to="/history">
|
||||||
<Trans i18nKey="history"/>
|
<Trans i18nKey="history"/>
|
||||||
</HeaderNavLink>
|
</HeaderNavLink>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-inline-flex">
|
<div className="d-inline-flex">
|
||||||
{user.status === LoginStatus.forbidden ?
|
{user.status === LoginStatus.forbidden
|
||||||
<Fragment>
|
? <Fragment>
|
||||||
<span className={"mr-1"}>
|
<span className={'mr-1'}>
|
||||||
<NewGuestNoteButton/>
|
<NewGuestNoteButton/>
|
||||||
</span>
|
</span>
|
||||||
<SignInButton/>
|
<SignInButton/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
:
|
: <Fragment>
|
||||||
<Fragment>
|
<span className={'mr-1'}>
|
||||||
<span className={"mr-1"}>
|
<NewUserNoteButton/>
|
||||||
<NewUserNoteButton/>
|
</span>
|
||||||
</span>
|
<UserDropdown/>
|
||||||
<UserDropdown/>
|
</Fragment>
|
||||||
</Fragment>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</Navbar>
|
||||||
</Navbar>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {HeaderBar}
|
export { HeaderBar }
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import {Nav} from "react-bootstrap";
|
import { Nav } from 'react-bootstrap'
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
|
|
||||||
export interface HeaderNavLinkProps {
|
export interface HeaderNavLinkProps {
|
||||||
to: string
|
to: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderNavLink: React.FC<HeaderNavLinkProps> = (props) => {
|
export const HeaderNavLink: React.FC<HeaderNavLinkProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<LinkContainer to={props.to}>
|
<LinkContainer to={props.to}>
|
||||||
<Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link>
|
<Nav.Link className="text-light" href={props.to}>{props.children}</Nav.Link>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
);
|
)
|
||||||
}
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import {Button} from "react-bootstrap";
|
import { Button } from 'react-bootstrap'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const NewGuestNoteButton: React.FC = () => {
|
export const NewGuestNoteButton: React.FC = () => {
|
||||||
const {i18n} = useTranslation();
|
const { i18n } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<LinkContainer to={'/new'} title={i18n.t("newGuestNote")}>
|
<LinkContainer to={'/new'} title={i18n.t('newGuestNote')}>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="d-inline-flex align-items-center">
|
className="d-inline-flex align-items-center">
|
||||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey='newGuestNote'/>
|
<Trans i18nKey='newGuestNote'/>
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</LinkContainer>)
|
</LinkContainer>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import {Button} from "react-bootstrap";
|
import { Button } from 'react-bootstrap'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const NewUserNoteButton: React.FC = () => {
|
export const NewUserNoteButton: React.FC = () => {
|
||||||
const {i18n} = useTranslation()
|
const { i18n } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<LinkContainer to={'/new'} title={i18n.t("newNote")}>
|
<LinkContainer to={'/new'} title={i18n.t('newNote')}>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="d-inline-flex align-items-center">
|
className="d-inline-flex align-items-center">
|
||||||
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
<FontAwesomeIcon icon="plus" className="mr-1"/>
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey='newNote'/>
|
<Trans i18nKey='newNote'/>
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Button} from "react-bootstrap";
|
import { Button } from 'react-bootstrap'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
|
|
||||||
export const SignInButton: React.FC = () => {
|
export const SignInButton: React.FC = () => {
|
||||||
const {i18n} = useTranslation();
|
const { i18n } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinkContainer to="/login" title={i18n.t("signIn")}>
|
<LinkContainer to="/login" title={i18n.t('signIn')}>
|
||||||
<Button
|
<Button
|
||||||
variant="success"
|
variant="success"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<Trans i18nKey="signIn"/>
|
<Trans i18nKey="signIn"/>
|
||||||
</Button>
|
</Button>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
import {Dropdown} from "react-bootstrap";
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {useSelector} from "react-redux";
|
import { useSelector } from 'react-redux'
|
||||||
import {ApplicationState} from "../../../../../redux";
|
import { ApplicationState } from '../../../../../redux'
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import { LinkContainer } from 'react-router-bootstrap'
|
||||||
import {clearUser} from "../../../../../redux/user/methods";
|
import { clearUser } from '../../../../../redux/user/methods'
|
||||||
import "./user-dropdown.scss";
|
import './user-dropdown.scss'
|
||||||
import {Trans} from "react-i18next";
|
import { Trans } from 'react-i18next'
|
||||||
|
|
||||||
export const UserDropdown: React.FC = () => {
|
export const UserDropdown: React.FC = () => {
|
||||||
const user = useSelector((state: ApplicationState) => state.user);
|
const user = useSelector((state: ApplicationState) => state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown alignRight>
|
<Dropdown alignRight>
|
||||||
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic">
|
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic">
|
||||||
<div className='d-inline-flex align-items-baseline'>
|
<div className='d-inline-flex align-items-baseline'>
|
||||||
<img
|
<img
|
||||||
src={user.photo}
|
src={user.photo}
|
||||||
className="user-avatar"
|
className="user-avatar"
|
||||||
alt={`Avatar of ${user.name}`}
|
alt={`Avatar of ${user.name}`}
|
||||||
/><span>{user.name}</span>
|
/><span>{user.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<LinkContainer to={`/features`}>
|
<LinkContainer to={'/features'}>
|
||||||
<Dropdown.Item>
|
<Dropdown.Item>
|
||||||
<FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/>
|
<FontAwesomeIcon icon="bolt" fixedWidth={true} className="mr-2"/>
|
||||||
<Trans i18nKey="features"/>
|
<Trans i18nKey="features"/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
<LinkContainer to={`/me/export`}>
|
<LinkContainer to={'/me/export'}>
|
||||||
<Dropdown.Item>
|
<Dropdown.Item>
|
||||||
<FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/>
|
<FontAwesomeIcon icon="cloud-download-alt" fixedWidth={true} className="mr-2"/>
|
||||||
<Trans i18nKey="exportUserData"/>
|
<Trans i18nKey="exportUserData"/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
<Dropdown.Item href="#">
|
<Dropdown.Item href="#">
|
||||||
<FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/>
|
<FontAwesomeIcon icon="trash" fixedWidth={true} className="mr-2"/>
|
||||||
<Trans i18nKey="deleteUser"/>
|
<Trans i18nKey="deleteUser"/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clearUser();
|
clearUser()
|
||||||
}}>
|
}}>
|
||||||
<FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/>
|
<FontAwesomeIcon icon="sign-out-alt" fixedWidth={true} className="mr-2"/>
|
||||||
<Trans i18nKey="signOut"/>
|
<Trans i18nKey="signOut"/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>)
|
</Dropdown>)
|
||||||
};
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
font-family: 'Source Sans Pro';
|
font-family: 'Source Sans Pro';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
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;
|
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 */
|
/* vietnamese */
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import React from 'react'
|
||||||
import React from "react";
|
import { Button } from 'react-bootstrap'
|
||||||
import "./close-button.scss"
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {Button} from "react-bootstrap";
|
import './close-button.scss'
|
||||||
|
|
||||||
export interface CloseButtonProps {
|
export interface CloseButtonProps {
|
||||||
isDark: boolean;
|
isDark: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CloseButton: React.FC<CloseButtonProps> = ({isDark}) => {
|
const CloseButton: React.FC<CloseButtonProps> = ({ isDark }) => {
|
||||||
return (
|
return (
|
||||||
<Button variant={isDark ? "secondary" : "light"}>
|
<Button variant={isDark ? 'secondary' : 'light'}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className="history-close"
|
className="history-close"
|
||||||
icon="times"
|
icon="times"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {CloseButton}
|
export { CloseButton }
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import "./pin-button.scss"
|
import './pin-button.scss'
|
||||||
import {Button} from "react-bootstrap";
|
import { Button } from 'react-bootstrap'
|
||||||
|
|
||||||
export interface PinButtonProps {
|
export interface PinButtonProps {
|
||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
onPinClick: () => void;
|
onPinClick: () => void;
|
||||||
isDark: boolean;
|
isDark: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PinButton: React.FC<PinButtonProps> = ({isPinned, onPinClick, isDark}) => {
|
export const PinButton: React.FC<PinButtonProps> = ({ isPinned, onPinClick, isDark }) => {
|
||||||
return (
|
return (
|
||||||
<Button variant={isDark ? "secondary" : "light"}
|
<Button variant={isDark ? 'secondary' : 'light'}
|
||||||
onClick={onPinClick}>
|
onClick={onPinClick}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon="thumbtack"
|
icon="thumbtack"
|
||||||
className={`history-pin ${isPinned ? 'active' : ''}`}
|
className={`history-pin ${isPinned ? 'active' : ''}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
import { HistoryEntriesProps } from '../history-content/history-content'
|
||||||
import {HistoryCard} from "./history-card";
|
import { HistoryCard } from './history-card'
|
||||||
import {Pager} from '../../../../pagination/pager';
|
import { Pager } from '../../../../pagination/pager'
|
||||||
import {Row} from 'react-bootstrap';
|
import { Row } from 'react-bootstrap'
|
||||||
|
|
||||||
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
export const HistoryCardList: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => {
|
||||||
|
return (
|
||||||
return (
|
<Row className="justify-content-center">
|
||||||
<Row className="justify-content-center">
|
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
{
|
||||||
{
|
entries.map((entry) => (
|
||||||
entries.map((entry) => (
|
<HistoryCard
|
||||||
<HistoryCard
|
key={entry.id}
|
||||||
key={entry.id}
|
entry={entry}
|
||||||
entry={entry}
|
onPinClick={onPinClick}
|
||||||
onPinClick={onPinClick}
|
/>))
|
||||||
/>))
|
}
|
||||||
}
|
</Pager>
|
||||||
</Pager>
|
</Row>
|
||||||
</Row>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Badge, Card} from 'react-bootstrap'
|
import { Badge, Card } from 'react-bootstrap'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {PinButton} from "../common/pin-button";
|
import { PinButton } from '../common/pin-button'
|
||||||
import {CloseButton} from "../common/close-button";
|
import { CloseButton } from '../common/close-button'
|
||||||
import moment from "moment";
|
import moment from 'moment'
|
||||||
import {useTranslation} from "react-i18next";
|
import { useTranslation } from 'react-i18next'
|
||||||
import {HistoryEntryProps} from "../history-content/history-content";
|
import { HistoryEntryProps } from '../history-content/history-content'
|
||||||
import {formatHistoryDate} from "../../../../../utils/historyUtils";
|
import { formatHistoryDate } from '../../../../../utils/historyUtils'
|
||||||
|
|
||||||
export const HistoryCard: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => {
|
export const HistoryCard: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
<div className="p-2 col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
||||||
<Card className="p-0" text={"dark"} bg={"light"}>
|
<Card className="p-0" text={'dark'} bg={'light'}>
|
||||||
<div className="d-flex justify-content-between p-2 align-items-start">
|
<div className="d-flex justify-content-between p-2 align-items-start">
|
||||||
<PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => {
|
<PinButton isDark={false} isPinned={entry.pinned} onPinClick={() => {
|
||||||
onPinClick(entry.id)
|
onPinClick(entry.id)
|
||||||
}}/>
|
}}/>
|
||||||
<Card.Title className="m-0 mt-3">{entry.title}</Card.Title>
|
<Card.Title className="m-0 mt-3">{entry.title}</Card.Title>
|
||||||
<CloseButton isDark={false}/>
|
<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>
|
</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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import React, {Fragment, useState} from "react";
|
import React, { Fragment, useState } from 'react'
|
||||||
import {HistoryEntry, pinClick} from "../history";
|
import { HistoryEntry, pinClick } from '../history'
|
||||||
import {HistoryTable} from "../history-table/history-table";
|
import { HistoryTable } from '../history-table/history-table'
|
||||||
import {Alert, Row} from "react-bootstrap";
|
import { Alert, Row } from 'react-bootstrap'
|
||||||
import {Trans} from "react-i18next";
|
import { Trans } from 'react-i18next'
|
||||||
import {HistoryCardList} from "../history-card/history-card-list";
|
import { HistoryCardList } from '../history-card/history-card-list'
|
||||||
import {ViewStateEnum} from "../history-toolbar/history-toolbar";
|
import { ViewStateEnum } from '../history-toolbar/history-toolbar'
|
||||||
import {PagerPagination} from "../../../../pagination/pager-pagination";
|
import { PagerPagination } from '../../../../pagination/pager-pagination'
|
||||||
|
|
||||||
export interface HistoryContentProps {
|
export interface HistoryContentProps {
|
||||||
viewState: ViewStateEnum
|
viewState: ViewStateEnum
|
||||||
entries: HistoryEntry[]
|
entries: HistoryEntry[]
|
||||||
onPinClick: pinClick
|
onPinClick: pinClick
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryEntryProps {
|
export interface HistoryEntryProps {
|
||||||
entry: HistoryEntry,
|
entry: HistoryEntry,
|
||||||
onPinClick: pinClick
|
onPinClick: pinClick
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryEntriesProps {
|
export interface HistoryEntriesProps {
|
||||||
|
@ -25,39 +25,38 @@ export interface HistoryEntriesProps {
|
||||||
onLastPageIndexChange: (lastPageIndex: number) => void
|
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}) => {
|
if (entries.length === 0) {
|
||||||
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}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Row className={'justify-content-center'}>
|
||||||
{mapViewStateToComponent(viewState)}
|
<Alert variant={'secondary'}>
|
||||||
<Row className="justify-content-center">
|
<Trans i18nKey={'noHistory'}/>
|
||||||
<PagerPagination numberOfPageButtonsToShowAfterAndBeforeCurrent={2} lastPageIndex={lastPageIndex}
|
</Alert>
|
||||||
onPageChange={setPageIndex}/>
|
</Row>
|
||||||
</Row>
|
)
|
||||||
</Fragment>);
|
}
|
||||||
|
|
||||||
|
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>)
|
||||||
}
|
}
|
|
@ -1,30 +1,30 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {PinButton} from "../common/pin-button";
|
import { PinButton } from '../common/pin-button'
|
||||||
import {CloseButton} from "../common/close-button";
|
import { CloseButton } from '../common/close-button'
|
||||||
import {useTranslation} from "react-i18next";
|
import { useTranslation } from 'react-i18next'
|
||||||
import {HistoryEntryProps} from "../history-content/history-content";
|
import { HistoryEntryProps } from '../history-content/history-content'
|
||||||
import {formatHistoryDate} from "../../../../../utils/historyUtils";
|
import { formatHistoryDate } from '../../../../../utils/historyUtils'
|
||||||
import {Badge} from "react-bootstrap";
|
import { Badge } from 'react-bootstrap'
|
||||||
|
|
||||||
export const HistoryTableRow: React.FC<HistoryEntryProps> = ({entry, onPinClick}) => {
|
export const HistoryTableRow: React.FC<HistoryEntryProps> = ({ entry, onPinClick }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{entry.title}</td>
|
<td>{entry.title}</td>
|
||||||
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
<td>{formatHistoryDate(entry.lastVisited)}</td>
|
||||||
<td>
|
<td>
|
||||||
{
|
{
|
||||||
entry.tags.map((tag) => <Badge variant={"dark"} className={"mr-1 mb-1"}
|
entry.tags.map((tag) => <Badge variant={'dark'} className={'mr-1 mb-1'}
|
||||||
key={tag}>{tag}</Badge>)
|
key={tag}>{tag}</Badge>)
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => {
|
<PinButton isDark={true} isPinned={entry.pinned} onPinClick={() => {
|
||||||
onPinClick(entry.id)
|
onPinClick(entry.id)
|
||||||
}}/>
|
}}/>
|
||||||
|
|
||||||
<CloseButton isDark={true}/>
|
<CloseButton isDark={true}/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {Table} from "react-bootstrap"
|
import { Table } from 'react-bootstrap'
|
||||||
import {HistoryTableRow} from "./history-table-row";
|
import { HistoryTableRow } from './history-table-row'
|
||||||
import {HistoryEntriesProps} from "../history-content/history-content";
|
import { HistoryEntriesProps } from '../history-content/history-content'
|
||||||
import {Trans} from "react-i18next";
|
import { Trans } from 'react-i18next'
|
||||||
import {Pager} from "../../../../pagination/pager";
|
import { Pager } from '../../../../pagination/pager'
|
||||||
|
|
||||||
export const HistoryTable: React.FC<HistoryEntriesProps> = ({entries, onPinClick, pageIndex, onLastPageIndexChange}) => {
|
export const HistoryTable: React.FC<HistoryEntriesProps> = ({ entries, onPinClick, pageIndex, onLastPageIndexChange }) => {
|
||||||
return (
|
return (
|
||||||
<Table striped bordered hover size="sm" variant="dark">
|
<Table striped bordered hover size="sm" variant="dark">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><Trans i18nKey={"title"}/></th>
|
<th><Trans i18nKey={'title'}/></th>
|
||||||
<th><Trans i18nKey={"lastVisit"}/></th>
|
<th><Trans i18nKey={'lastVisit'}/></th>
|
||||||
<th><Trans i18nKey={"tags"}/></th>
|
<th><Trans i18nKey={'tags'}/></th>
|
||||||
<th><Trans i18nKey={"actions"}/></th>
|
<th><Trans i18nKey={'actions'}/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
<Pager numberOfElementsPerPage={6} pageIndex={pageIndex} onLastPageIndexChange={onLastPageIndexChange}>
|
||||||
{
|
{
|
||||||
entries.map((entry) =>
|
entries.map((entry) =>
|
||||||
<HistoryTableRow
|
<HistoryTableRow
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
onPinClick={onPinClick}
|
onPinClick={onPinClick}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
</Pager>
|
</Pager>
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import {Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup} from "react-bootstrap";
|
import React, { ChangeEvent, useEffect, useState } from 'react'
|
||||||
import React, {ChangeEvent, useEffect, useState} from "react";
|
import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {SortButton, SortModeEnum} from "../../../../sort-button/sort-button";
|
import { SortButton, SortModeEnum } from '../../../../sort-button/sort-button'
|
||||||
import {Typeahead} from 'react-bootstrap-typeahead';
|
import { Typeahead } from 'react-bootstrap-typeahead'
|
||||||
import "./typeahead-hacks.scss";
|
import './typeahead-hacks.scss'
|
||||||
|
|
||||||
export type HistoryToolbarChange = (settings: HistoryToolbarState) => void;
|
export type HistoryToolbarChange = (settings: HistoryToolbarState) => void;
|
||||||
|
|
||||||
export interface HistoryToolbarState {
|
export interface HistoryToolbarState {
|
||||||
viewState: ViewStateEnum
|
viewState: ViewStateEnum
|
||||||
titleSortDirection: SortModeEnum
|
titleSortDirection: SortModeEnum
|
||||||
lastVisitedSortDirection: SortModeEnum
|
lastVisitedSortDirection: SortModeEnum
|
||||||
keywordSearch: string
|
keywordSearch: string
|
||||||
selectedTags: string[]
|
selectedTags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ViewStateEnum {
|
export enum ViewStateEnum {
|
||||||
|
@ -27,102 +27,101 @@ export interface HistoryToolbarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initState: HistoryToolbarState = {
|
export const initState: HistoryToolbarState = {
|
||||||
viewState: ViewStateEnum.card,
|
viewState: ViewStateEnum.card,
|
||||||
titleSortDirection: SortModeEnum.no,
|
titleSortDirection: SortModeEnum.no,
|
||||||
lastVisitedSortDirection: SortModeEnum.no,
|
lastVisitedSortDirection: SortModeEnum.no,
|
||||||
keywordSearch: "",
|
keywordSearch: '',
|
||||||
selectedTags: []
|
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 titleSortChanged = (direction: SortModeEnum) => {
|
||||||
const [state, setState] = useState<HistoryToolbarState>(initState);
|
setState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
titleSortDirection: direction,
|
||||||
|
lastVisitedSortDirection: SortModeEnum.no
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const titleSortChanged = (direction: SortModeEnum) => {
|
const lastVisitedSortChanged = (direction: SortModeEnum) => {
|
||||||
setState(prevState => ({
|
setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
titleSortDirection: direction,
|
lastVisitedSortDirection: direction,
|
||||||
lastVisitedSortDirection: SortModeEnum.no
|
titleSortDirection: SortModeEnum.no
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastVisitedSortChanged = (direction: SortModeEnum) => {
|
const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setState(prevState => ({
|
setState(prevState => ({ ...prevState, keywordSearch: event.currentTarget.value }))
|
||||||
...prevState,
|
}
|
||||||
lastVisitedSortDirection: direction,
|
|
||||||
titleSortDirection: SortModeEnum.no
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
const toggleViewChanged = (newViewState: ViewStateEnum) => {
|
||||||
setState(prevState => ({...prevState, keywordSearch: event.currentTarget.value}));
|
setState((prevState) => ({ ...prevState, viewState: newViewState }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleViewChanged = (newViewState: ViewStateEnum) => {
|
const selectedTagsChanged = (selected: string[]) => {
|
||||||
setState((prevState) => ({...prevState, viewState: newViewState}))
|
setState(prevState => ({ ...prevState, selectedTags: selected }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedTagsChanged = (selected: string[]) => {
|
useEffect(() => {
|
||||||
setState((prevState => ({...prevState, selectedTags: selected})))
|
onSettingsChange(state)
|
||||||
}
|
}, [onSettingsChange, state])
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
onSettingsChange(state);
|
<Form inline={true}>
|
||||||
}, [onSettingsChange, state])
|
<InputGroup className={'mr-1'}>
|
||||||
|
<Typeahead id={'tagsSelection'} options={tags} multiple={true} placeholder={t('selectTags')}
|
||||||
return (
|
onChange={selectedTagsChanged}/>
|
||||||
<Form inline={true}>
|
</InputGroup>
|
||||||
<InputGroup className={"mr-1"}>
|
<InputGroup className={'mr-1'}>
|
||||||
<Typeahead id={"tagsSelection"} options={tags} multiple={true} placeholder={t("selectTags")}
|
<FormControl
|
||||||
onChange={selectedTagsChanged}/>
|
placeholder={t('searchKeywords')}
|
||||||
</InputGroup>
|
aria-label={t('searchKeywords')}
|
||||||
<InputGroup className={"mr-1"}>
|
onChange={keywordSearchChanged}
|
||||||
<FormControl
|
/>
|
||||||
placeholder={t("searchKeywords")}
|
</InputGroup>
|
||||||
aria-label={t("searchKeywords")}
|
<InputGroup className={'mr-1'}>
|
||||||
onChange={keywordSearchChanged}
|
<SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={'light'}><Trans
|
||||||
/>
|
i18nKey={'sortByTitle'}/></SortButton>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<InputGroup className={"mr-1"}>
|
<InputGroup className={'mr-1'}>
|
||||||
<SortButton onChange={titleSortChanged} direction={state.titleSortDirection} variant={"light"}><Trans
|
<SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection}
|
||||||
i18nKey={"sortByTitle"}/></SortButton>
|
variant={'light'}><Trans i18nKey={'sortByLastVisited'}/></SortButton>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<InputGroup className={"mr-1"}>
|
<InputGroup className={'mr-1'}>
|
||||||
<SortButton onChange={lastVisitedSortChanged} direction={state.lastVisitedSortDirection}
|
<Button variant={'light'} title={t('exportHistory')}>
|
||||||
variant={"light"}><Trans i18nKey={"sortByLastVisited"}/></SortButton>
|
<FontAwesomeIcon icon={'download'}/>
|
||||||
</InputGroup>
|
</Button>
|
||||||
<InputGroup className={"mr-1"}>
|
</InputGroup>
|
||||||
<Button variant={"light"} title={t("exportHistory")}>
|
<InputGroup className={'mr-1'}>
|
||||||
<FontAwesomeIcon icon={"download"}/>
|
<Button variant={'light'} title={t('importHistory')}>
|
||||||
</Button>
|
<FontAwesomeIcon icon={'upload'}/>
|
||||||
</InputGroup>
|
</Button>
|
||||||
<InputGroup className={"mr-1"}>
|
</InputGroup>
|
||||||
<Button variant={"light"} title={t("importHistory")}>
|
<InputGroup className={'mr-1'}>
|
||||||
<FontAwesomeIcon icon={"upload"}/>
|
<Button variant={'light'} title={t('clearHistory')}>
|
||||||
</Button>
|
<FontAwesomeIcon icon={'trash'}/>
|
||||||
</InputGroup>
|
</Button>
|
||||||
<InputGroup className={"mr-1"}>
|
</InputGroup>
|
||||||
<Button variant={"light"} title={t("clearHistory")}>
|
<InputGroup className={'mr-1'}>
|
||||||
<FontAwesomeIcon icon={"trash"}/>
|
<Button variant={'light'} title={t('refreshHistory')}>
|
||||||
</Button>
|
<FontAwesomeIcon icon={'sync'}/>
|
||||||
</InputGroup>
|
</Button>
|
||||||
<InputGroup className={"mr-1"}>
|
</InputGroup>
|
||||||
<Button variant={"light"} title={t("refreshHistory")}>
|
<InputGroup className={'mr-1'}>
|
||||||
<FontAwesomeIcon icon={"sync"}/>
|
<ToggleButtonGroup type="radio" name="options" value={state.viewState}
|
||||||
</Button>
|
onChange={(newViewState: ViewStateEnum) => {
|
||||||
</InputGroup>
|
toggleViewChanged(newViewState)
|
||||||
<InputGroup className={"mr-1"}>
|
}}>
|
||||||
<ToggleButtonGroup type="radio" name="options" value={state.viewState}
|
<ToggleButton className={'btn-light'} value={ViewStateEnum.card}><Trans
|
||||||
onChange={(newViewState: ViewStateEnum) => {
|
i18nKey={'cards'}/></ToggleButton>
|
||||||
toggleViewChanged(newViewState)
|
<ToggleButton className={'btn-light'} value={ViewStateEnum.table}><Trans
|
||||||
}}>
|
i18nKey={'table'}/></ToggleButton>
|
||||||
<ToggleButton className={"btn-light"} value={ViewStateEnum.card}><Trans
|
</ToggleButtonGroup>
|
||||||
i18nKey={"cards"}/></ToggleButton>
|
</InputGroup>
|
||||||
<ToggleButton className={"btn-light"} value={ViewStateEnum.table}><Trans
|
</Form>
|
||||||
i18nKey={"table"}/></ToggleButton>
|
)
|
||||||
</ToggleButtonGroup>
|
|
||||||
</InputGroup>
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
}
|
}
|
|
@ -1,67 +1,67 @@
|
||||||
import React, {Fragment, useEffect, useState} from 'react'
|
import React, { Fragment, useEffect, useState } from 'react'
|
||||||
import {HistoryContent} from './history-content/history-content';
|
import { Row } from 'react-bootstrap'
|
||||||
import {HistoryToolbar, HistoryToolbarState, initState as toolbarInitState} from './history-toolbar/history-toolbar';
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {loadHistoryFromLocalStore, sortAndFilterEntries} from "../../../../utils/historyUtils";
|
import { loadHistoryFromLocalStore, sortAndFilterEntries } from '../../../../utils/historyUtils'
|
||||||
import {Row} from 'react-bootstrap';
|
import { HistoryContent } from './history-content/history-content'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { HistoryToolbar, HistoryToolbarState, initState as toolbarInitState } from './history-toolbar/history-toolbar'
|
||||||
|
|
||||||
export interface HistoryEntry {
|
export interface HistoryEntry {
|
||||||
id: string,
|
id: string,
|
||||||
title: string,
|
title: string,
|
||||||
lastVisited: Date,
|
lastVisited: Date,
|
||||||
tags: string[],
|
tags: string[],
|
||||||
pinned: boolean
|
pinned: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type pinClick = (entryId: string) => void;
|
export type pinClick = (entryId: string) => void;
|
||||||
|
|
||||||
export const History: React.FC = () => {
|
export const History: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([])
|
const [historyEntries, setHistoryEntries] = useState<HistoryEntry[]>([])
|
||||||
const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState)
|
const [viewState, setViewState] = useState<HistoryToolbarState>(toolbarInitState)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const history = loadHistoryFromLocalStore();
|
const history = loadHistoryFromLocalStore()
|
||||||
setHistoryEntries(history);
|
setHistoryEntries(history)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (historyEntries === []) {
|
if (historyEntries === []) {
|
||||||
return;
|
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;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
window.localStorage.setItem('history', JSON.stringify(historyEntries))
|
||||||
|
}, [historyEntries])
|
||||||
|
|
||||||
const tags = historyEntries.map(entry => entry.tags)
|
const pinClick: pinClick = (entryId: string) => {
|
||||||
.reduce((a, b) => ([...a, ...b]), [])
|
setHistoryEntries((entries) => {
|
||||||
.filter((value, index, array) => {
|
return entries.map((entry) => {
|
||||||
if (index === 0) {
|
if (entry.id === entryId) {
|
||||||
return true;
|
entry.pinned = !entry.pinned
|
||||||
}
|
}
|
||||||
return (value !== array[index - 1])
|
return entry
|
||||||
})
|
})
|
||||||
const entriesToShow = sortAndFilterEntries(historyEntries, viewState);
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
const tags = historyEntries.map(entry => entry.tags)
|
||||||
<Fragment>
|
.reduce((a, b) => ([...a, ...b]), [])
|
||||||
<h1 className="mb-4"><Trans i18nKey="history"/></h1>
|
.filter((value, index, array) => {
|
||||||
<Row className={"justify-content-center mb-3"}>
|
if (index === 0) {
|
||||||
<HistoryToolbar onSettingsChange={setViewState} tags={tags}/>
|
return true
|
||||||
</Row>
|
}
|
||||||
<HistoryContent viewState={viewState.viewState}
|
return (value !== array[index - 1])
|
||||||
entries={entriesToShow}
|
})
|
||||||
onPinClick={pinClick}/>
|
const entriesToShow = sortAndFilterEntries(historyEntries, viewState)
|
||||||
</Fragment>
|
|
||||||
)
|
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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
import {LoginStatus} from "../../../../../redux/user/types";
|
import React from 'react'
|
||||||
import {Link} from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
import {Button} from "react-bootstrap";
|
import { Button } from 'react-bootstrap'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { LoginStatus } from '../../../../../redux/user/types'
|
||||||
import React from "react";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {useSelector} from "react-redux";
|
import { useSelector } from 'react-redux'
|
||||||
import {ApplicationState} from "../../../../../redux";
|
import { ApplicationState } from '../../../../../redux'
|
||||||
import "./cover-buttons.scss";
|
import './cover-buttons.scss'
|
||||||
|
|
||||||
export const CoverButtons: React.FC = () => {
|
export const CoverButtons: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
const user = useSelector((state: ApplicationState) => state.user);
|
const user = useSelector((state: ApplicationState) => state.user)
|
||||||
|
|
||||||
if (user.status === LoginStatus.ok) {
|
if (user.status === LoginStatus.ok) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<Link to="/login">
|
<Link to="/login">
|
||||||
<Button
|
<Button
|
||||||
className="cover-button"
|
className="cover-button"
|
||||||
variant="success"
|
variant="success"
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
<Trans i18nKey="signIn"/>
|
<Trans i18nKey="signIn"/>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<span className="m-2">
|
<span className="m-2">
|
||||||
<Trans i18nKey="or"/>
|
<Trans i18nKey="or"/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Link to="/features">
|
|
||||||
<Button
|
|
||||||
className="cover-button"
|
|
||||||
variant="primary"
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
<Trans i18nKey="exploreFeatures"/>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
<Link to="/features">
|
||||||
|
<Button
|
||||||
|
className="cover-button"
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<Trans i18nKey="exploreFeatures"/>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
import {Col, Row} from "react-bootstrap";
|
import React from 'react'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { Link } from 'react-router-dom'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Col, Row } from 'react-bootstrap'
|
||||||
import React from "react";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { Link } from "react-router-dom";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const FeatureLinks: React.FC = () => {
|
export const FeatureLinks: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
return (
|
return (
|
||||||
<Row className="mb-5">
|
<Row className="mb-5">
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Link to={"/features#Share-Notes"} className="text-light">
|
<Link to={'/features#Share-Notes'} className="text-light">
|
||||||
<FontAwesomeIcon icon="bolt" size="3x"/>
|
<FontAwesomeIcon icon="bolt" size="3x"/>
|
||||||
<h5>
|
<h5>
|
||||||
<Trans i18nKey="featureCollaboration"/>
|
<Trans i18nKey="featureCollaboration"/>
|
||||||
</h5>
|
</h5>
|
||||||
</Link>
|
</Link>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Link to={"/features#MathJax"} className="text-light">
|
<Link to={'/features#MathJax'} className="text-light">
|
||||||
<FontAwesomeIcon icon="chart-bar" size="3x"/>
|
<FontAwesomeIcon icon="chart-bar" size="3x"/>
|
||||||
<h5>
|
<h5>
|
||||||
<Trans i18nKey="featureMathJax"/>
|
<Trans i18nKey="featureMathJax"/>
|
||||||
</h5>
|
</h5>
|
||||||
</Link>
|
</Link>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Link to={"/features#Slide-Mode"} className="text-light">
|
<Link to={'/features#Slide-Mode'} className="text-light">
|
||||||
<FontAwesomeIcon icon="tv" size="3x"/>
|
<FontAwesomeIcon icon="tv" size="3x"/>
|
||||||
<h5>
|
<h5>
|
||||||
<Trans i18nKey="featureSlides"/>
|
<Trans i18nKey="featureSlides"/>
|
||||||
</h5>
|
</h5>
|
||||||
</Link>
|
</Link>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import screenshot from './img/screenshot.png';
|
import screenshot from './img/screenshot.png'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {FeatureLinks} from "./feature-links";
|
import { FeatureLinks } from './feature-links'
|
||||||
import {CoverButtons} from "./cover-buttons/cover-buttons";
|
import { CoverButtons } from './cover-buttons/cover-buttons'
|
||||||
|
|
||||||
const Intro: React.FC = () => {
|
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 (
|
<CoverButtons/>
|
||||||
<div>
|
|
||||||
<h1>
|
|
||||||
<FontAwesomeIcon icon="file-alt"/> CodiMD
|
|
||||||
</h1>
|
|
||||||
<p className="lead mb-5">
|
|
||||||
<Trans i18nKey="coverSlogan"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<CoverButtons/>
|
<img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/>
|
||||||
|
<FeatureLinks/>
|
||||||
<img alt="CodiMD Screenshot" src={screenshot} className="img-fluid mb-5"/>
|
</div>
|
||||||
<FeatureLinks/>
|
)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Intro}
|
export { Intro }
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import "./social-link-button.scss";
|
import './social-link-button.scss'
|
||||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
import { IconProp } from '../../../../../../utils/iconProp'
|
||||||
|
|
||||||
export interface SocialButtonProps {
|
export interface SocialButtonProps {
|
||||||
backgroundClass: string,
|
backgroundClass: string,
|
||||||
href: string
|
href: string
|
||||||
icon: IconProp
|
icon: IconProp
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SocialLinkButton: React.FC<SocialButtonProps> = ({title, backgroundClass, href, icon, children}) => {
|
export const SocialLinkButton: React.FC<SocialButtonProps> = ({ title, backgroundClass, href, icon, children }) => {
|
||||||
return (
|
return (
|
||||||
<a href={href} title={title}
|
<a href={href} title={title}
|
||||||
className={"btn social-link-button p-0 d-inline-flex align-items-stretch " + backgroundClass}>
|
className={'btn social-link-button p-0 d-inline-flex align-items-stretch ' + backgroundClass}>
|
||||||
<span className="icon-part d-flex align-items-center">
|
<span className="icon-part d-flex align-items-center">
|
||||||
<FontAwesomeIcon icon={icon} className={"social-icon"} fixedWidth={true}/>
|
<FontAwesomeIcon icon={icon} className={'social-icon'} fixedWidth={true}/>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-part d-flex align-items-center mx-auto">
|
<span className="text-part d-flex align-items-center mx-auto">
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,64 @@
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {Alert, Button, Card, Form} from "react-bootstrap";
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
import React, {useState} from "react";
|
import React, { FormEvent, useState } from 'react'
|
||||||
import {postEmailLogin} from "../../../../../api/user";
|
import { postEmailLogin } from '../../../../../api/user'
|
||||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||||
|
|
||||||
export const ViaEMail: React.FC = () => {
|
export const ViaEMail: React.FC = () => {
|
||||||
const {t} = useTranslation();
|
const { t } = useTranslation()
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState('')
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState('')
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
const doAsyncLogin = () => {
|
const doAsyncLogin = async () => {
|
||||||
(async () => {
|
await postEmailLogin(email, password)
|
||||||
try {
|
await getAndSetUser()
|
||||||
await postEmailLogin(email, password);
|
}
|
||||||
await getAndSetUser();
|
|
||||||
} catch {
|
|
||||||
setError(true);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFormSubmit = (event: any) => {
|
const onFormSubmit = (event: FormEvent) => {
|
||||||
doAsyncLogin();
|
doAsyncLogin().catch(() => setError(true))
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-dark mb-4">
|
<Card className="bg-dark mb-4">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<Trans i18nKey="signInVia" values={{service: "E-Mail"}}/>
|
<Trans i18nKey="signInVia" values={{ service: 'E-Mail' }}/>
|
||||||
</Card.Title>
|
</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="password">
|
||||||
<Form.Group controlId="email">
|
<Form.Control
|
||||||
<Form.Control
|
isInvalid={error}
|
||||||
isInvalid={error}
|
type="password"
|
||||||
type="email"
|
size="sm"
|
||||||
size="sm"
|
placeholder={t('password')}
|
||||||
placeholder={t("email")}
|
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||||
onChange={(event) => setEmail(event.currentTarget.value)}
|
className="bg-dark text-white"/>
|
||||||
className="bg-dark text-white"
|
</Form.Group>
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group controlId="password">
|
<Alert className="small" show={error} variant="danger">
|
||||||
<Form.Control
|
<Trans i18nKey="errorEmailLogin"/>
|
||||||
isInvalid={error}
|
</Alert>
|
||||||
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">
|
<Button
|
||||||
<Trans i18nKey="errorEmailLogin"/>
|
type="submit"
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Button
|
variant="primary">
|
||||||
type="submit"
|
<Trans i18nKey="signIn"/>
|
||||||
variant="primary">
|
</Button>
|
||||||
<Trans i18nKey="signIn"/>
|
</Form>
|
||||||
</Button>
|
</Card.Body>
|
||||||
</Form>
|
</Card>
|
||||||
</Card.Body>
|
)
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,80 +1,74 @@
|
||||||
import React, {useState} from "react";
|
import React, { FormEvent, 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";
|
|
||||||
|
|
||||||
const ViaLdap: React.FC = () => {
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
const {t} = useTranslation();
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap);
|
import { postLdapLogin } from '../../../../../api/user'
|
||||||
|
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../../../../redux'
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
export const ViaLdap: React.FC = () => {
|
||||||
const [password, setPassword] = useState("");
|
const { t } = useTranslation()
|
||||||
const [error, setError] = useState(false);
|
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 = () => {
|
const name = ldapCustomName ? `${ldapCustomName} (LDAP)` : 'LDAP'
|
||||||
(async () => {
|
|
||||||
try {
|
const doAsyncLogin = async () => {
|
||||||
await postLdapLogin(username, password);
|
try {
|
||||||
await getAndSetUser();
|
await postLdapLogin(username, password)
|
||||||
} catch {
|
await getAndSetUser()
|
||||||
setError(true);
|
} catch {
|
||||||
}
|
setError(true)
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onFormSubmit = (event: any) => {
|
const onFormSubmit = (event: FormEvent) => {
|
||||||
doAsyncLogin();
|
doAsyncLogin().catch(() => setError(true))
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-dark mb-4">
|
<Card className="bg-dark mb-4">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<Trans i18nKey="signInVia" values={{service: name}}/>
|
<Trans i18nKey="signInVia" values={{ service: name }}/>
|
||||||
</Card.Title>
|
</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="password">
|
||||||
<Form.Group controlId="username">
|
<Form.Control
|
||||||
<Form.Control
|
isInvalid={error}
|
||||||
isInvalid={error}
|
type="password"
|
||||||
type="text"
|
size="sm"
|
||||||
size="sm"
|
placeholder={t('password')}
|
||||||
placeholder={t("username")}
|
onChange={(event) => setPassword(event.currentTarget.value)}
|
||||||
onChange={(event) => setUsername(event.currentTarget.value)}
|
className="bg-dark text-white"/>
|
||||||
className="bg-dark text-white"
|
</Form.Group>
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
|
|
||||||
<Form.Group controlId="password">
|
<Alert className="small" show={error} variant="danger">
|
||||||
<Form.Control
|
<Trans i18nKey="errorLdapLogin"/>
|
||||||
isInvalid={error}
|
</Alert>
|
||||||
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">
|
<Button
|
||||||
<Trans i18nKey="errorLdapLogin"/>
|
type="submit"
|
||||||
</Alert>
|
variant="primary">
|
||||||
|
<Trans i18nKey="signIn"/>
|
||||||
<Button
|
</Button>
|
||||||
type="submit"
|
</Form>
|
||||||
variant="primary">
|
</Card.Body>
|
||||||
<Trans i18nKey="signIn"/>
|
</Card>
|
||||||
</Button>
|
)
|
||||||
</Form>
|
}
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { ViaLdap }
|
|
||||||
|
|
|
@ -1,115 +1,115 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
import { IconProp } from '../../../../../utils/iconProp'
|
||||||
import {SocialLinkButton} from "./social-link-button/social-link-button";
|
import { SocialLinkButton } from './social-link-button/social-link-button'
|
||||||
|
|
||||||
export enum OneClickType {
|
export enum OneClickType {
|
||||||
'DROPBOX'="dropbox",
|
'DROPBOX' = 'dropbox',
|
||||||
'FACEBOOK'="facebook",
|
'FACEBOOK' = 'facebook',
|
||||||
'GITHUB'="github",
|
'GITHUB' = 'github',
|
||||||
'GITLAB'="gitlab",
|
'GITLAB' = 'gitlab',
|
||||||
'GOOGLE'="google",
|
'GOOGLE' = 'google',
|
||||||
'OAUTH2'="oauth2",
|
'OAUTH2' = 'oauth2',
|
||||||
'SAML'="saml",
|
'SAML' = 'saml',
|
||||||
'TWITTER'="twitter"
|
'TWITTER' = 'twitter'
|
||||||
}
|
}
|
||||||
|
|
||||||
type OneClick2Map = (oneClickType: OneClickType) => {
|
type OneClick2Map = (oneClickType: OneClickType) => {
|
||||||
name: string,
|
name: string,
|
||||||
icon: IconProp,
|
icon: IconProp,
|
||||||
className: string,
|
className: string,
|
||||||
url: string
|
url: string
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildBackendAuthUrl = (backendName: string) => {
|
const buildBackendAuthUrl = (backendName: string) => {
|
||||||
return `https://localhost:3000/auth/${backendName}`
|
return `https://localhost:3000/auth/${backendName}`
|
||||||
};
|
}
|
||||||
|
|
||||||
const getMetadata: OneClick2Map = (oneClickType: OneClickType) => {
|
const getMetadata: OneClick2Map = (oneClickType: OneClickType) => {
|
||||||
switch (oneClickType) {
|
switch (oneClickType) {
|
||||||
case OneClickType.DROPBOX:
|
case OneClickType.DROPBOX:
|
||||||
return {
|
return {
|
||||||
name: "Dropbox",
|
name: 'Dropbox',
|
||||||
icon: ["fab", "dropbox"],
|
icon: ['fab', 'dropbox'],
|
||||||
className: "btn-social-dropbox",
|
className: 'btn-social-dropbox',
|
||||||
url: buildBackendAuthUrl("dropbox")
|
url: buildBackendAuthUrl('dropbox')
|
||||||
}
|
}
|
||||||
case OneClickType.FACEBOOK:
|
case OneClickType.FACEBOOK:
|
||||||
return {
|
return {
|
||||||
name: "Facebook",
|
name: 'Facebook',
|
||||||
icon: ["fab", "facebook"],
|
icon: ['fab', 'facebook'],
|
||||||
className: "btn-social-facebook",
|
className: 'btn-social-facebook',
|
||||||
url: buildBackendAuthUrl("facebook")
|
url: buildBackendAuthUrl('facebook')
|
||||||
}
|
}
|
||||||
case OneClickType.GITHUB:
|
case OneClickType.GITHUB:
|
||||||
return {
|
return {
|
||||||
name: "GitHub",
|
name: 'GitHub',
|
||||||
icon: ["fab", "github"],
|
icon: ['fab', 'github'],
|
||||||
className: "btn-social-github",
|
className: 'btn-social-github',
|
||||||
url: buildBackendAuthUrl("github")
|
url: buildBackendAuthUrl('github')
|
||||||
}
|
}
|
||||||
case OneClickType.GITLAB:
|
case OneClickType.GITLAB:
|
||||||
return {
|
return {
|
||||||
name: "GitLab",
|
name: 'GitLab',
|
||||||
icon: ["fab", "gitlab"],
|
icon: ['fab', 'gitlab'],
|
||||||
className: "btn-social-gitlab",
|
className: 'btn-social-gitlab',
|
||||||
url: buildBackendAuthUrl("gitlab")
|
url: buildBackendAuthUrl('gitlab')
|
||||||
}
|
}
|
||||||
case OneClickType.GOOGLE:
|
case OneClickType.GOOGLE:
|
||||||
return {
|
return {
|
||||||
name: "Google",
|
name: 'Google',
|
||||||
icon: ["fab", "google"],
|
icon: ['fab', 'google'],
|
||||||
className: "btn-social-google",
|
className: 'btn-social-google',
|
||||||
url: buildBackendAuthUrl("google")
|
url: buildBackendAuthUrl('google')
|
||||||
}
|
}
|
||||||
case OneClickType.OAUTH2:
|
case OneClickType.OAUTH2:
|
||||||
return {
|
return {
|
||||||
name: "OAuth2",
|
name: 'OAuth2',
|
||||||
icon: "address-card",
|
icon: 'address-card',
|
||||||
className: "btn-primary",
|
className: 'btn-primary',
|
||||||
url: buildBackendAuthUrl("oauth2")
|
url: buildBackendAuthUrl('oauth2')
|
||||||
}
|
}
|
||||||
case OneClickType.SAML:
|
case OneClickType.SAML:
|
||||||
return {
|
return {
|
||||||
name: "SAML",
|
name: 'SAML',
|
||||||
icon: "users",
|
icon: 'users',
|
||||||
className: "btn-success",
|
className: 'btn-success',
|
||||||
url: buildBackendAuthUrl("saml")
|
url: buildBackendAuthUrl('saml')
|
||||||
}
|
}
|
||||||
case OneClickType.TWITTER:
|
case OneClickType.TWITTER:
|
||||||
return {
|
return {
|
||||||
name: "Twitter",
|
name: 'Twitter',
|
||||||
icon: ["fab", "twitter"],
|
icon: ['fab', 'twitter'],
|
||||||
className: "btn-social-twitter",
|
className: 'btn-social-twitter',
|
||||||
url: buildBackendAuthUrl("twitter")
|
url: buildBackendAuthUrl('twitter')
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: '',
|
||||||
icon: "exclamation",
|
icon: 'exclamation',
|
||||||
className: "",
|
className: '',
|
||||||
url: "#"
|
url: '#'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViaOneClickProps {
|
export interface ViaOneClickProps {
|
||||||
oneClickType: OneClickType;
|
oneClickType: OneClickType;
|
||||||
optionalName?: string;
|
optionalName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViaOneClick: React.FC<ViaOneClickProps> = ({oneClickType, optionalName}) => {
|
const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
||||||
const {name, icon, className, url} = getMetadata(oneClickType);
|
const { name, icon, className, url } = getMetadata(oneClickType)
|
||||||
const text = !!optionalName ? optionalName : name;
|
const text = optionalName || name
|
||||||
return (
|
return (
|
||||||
<SocialLinkButton
|
<SocialLinkButton
|
||||||
backgroundClass={className}
|
backgroundClass={className}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
href={url}
|
href={url}
|
||||||
title={text}
|
title={text}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</SocialLinkButton>
|
</SocialLinkButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ViaOneClick}
|
export { ViaOneClick }
|
||||||
|
|
|
@ -1,61 +1,53 @@
|
||||||
import React, {useState} from "react";
|
import React, { FormEvent, useState } from 'react'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {Alert, Button, Card, Form} from "react-bootstrap";
|
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||||
import {postOpenIdLogin} from "../../../../../api/user";
|
import { postOpenIdLogin } from '../../../../../api/user'
|
||||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||||
|
|
||||||
const ViaOpenId: React.FC = () => {
|
export const ViaOpenId: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
const [openId, setOpenId] = useState("");
|
const [openId, setOpenId] = useState('')
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false)
|
||||||
const doAsyncLogin = () => {
|
const doAsyncLogin: (() => Promise<void>) = async () => {
|
||||||
(async () => {
|
await postOpenIdLogin(openId)
|
||||||
try {
|
await getAndSetUser()
|
||||||
await postOpenIdLogin(openId);
|
}
|
||||||
await getAndSetUser();
|
|
||||||
} catch {
|
|
||||||
setError(true);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFormSubmit = (event: any) => {
|
const onFormSubmit = (event: FormEvent) => {
|
||||||
doAsyncLogin();
|
doAsyncLogin().catch(() => setError(true))
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-dark mb-4">
|
<Card className="bg-dark mb-4">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<Trans i18nKey="signInVia" values={{service: "OpenID"}}/>
|
<Trans i18nKey="signInVia" values={{ service: 'OpenID' }}/>
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
|
|
||||||
<Form onSubmit={onFormSubmit}>
|
<Form onSubmit={onFormSubmit}>
|
||||||
<Form.Group controlId="openid">
|
<Form.Group controlId="openid">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
isInvalid={error}
|
isInvalid={error}
|
||||||
type="text"
|
type="text"
|
||||||
size="sm"
|
size="sm"
|
||||||
placeholder={"OpenID"}
|
placeholder={'OpenID'}
|
||||||
onChange={(event) => setOpenId(event.currentTarget.value)}
|
onChange={(event) => setOpenId(event.currentTarget.value)}
|
||||||
className="bg-dark text-white"
|
className="bg-dark text-white"
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
<Alert className="small" show={error} variant="danger">
|
<Alert className="small" show={error} variant="danger">
|
||||||
<Trans i18nKey="errorOpenIdLogin"/>
|
<Trans i18nKey="errorOpenIdLogin"/>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="primary">
|
variant="primary">
|
||||||
<Trans i18nKey="signIn"/>
|
<Trans i18nKey="signIn"/>
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export { ViaOpenId }
|
|
||||||
|
|
|
@ -1,83 +1,81 @@
|
||||||
import React from "react"
|
import React from 'react'
|
||||||
import {Card, Col, Row} from "react-bootstrap"
|
import { Card, Col, Row } from 'react-bootstrap'
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {ViaEMail} from "./auth/via-email";
|
import { ViaEMail } from './auth/via-email'
|
||||||
import {OneClickType, ViaOneClick} from "./auth/via-one-click";
|
import { OneClickType, ViaOneClick } from './auth/via-one-click'
|
||||||
import {ViaLdap} from "./auth/via-ldap";
|
import { ViaLdap } from './auth/via-ldap'
|
||||||
import {useSelector} from "react-redux";
|
import { useSelector } from 'react-redux'
|
||||||
import {ApplicationState} from "../../../../redux";
|
import { ApplicationState } from '../../../../redux'
|
||||||
import {ViaOpenId} from "./auth/via-openid";
|
import { ViaOpenId } from './auth/via-openid'
|
||||||
import {Redirect} from "react-router";
|
import { Redirect } from 'react-router'
|
||||||
import {LoginStatus} from "../../../../redux/user/types";
|
import { LoginStatus } from '../../../../redux/user/types'
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
export const Login: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation()
|
||||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders);
|
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
|
||||||
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames);
|
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames)
|
||||||
const userLoginState = useSelector((state: ApplicationState) => state.user.status);
|
const userLoginState = useSelector((state: ApplicationState) => state.user.status)
|
||||||
const emailForm = authProviders.email ? <ViaEMail/> : null
|
const emailForm = authProviders.email ? <ViaEMail/> : null
|
||||||
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
||||||
const openIdForm = authProviders.openid ? <ViaOpenId/> : null
|
const openIdForm = authProviders.openid ? <ViaOpenId/> : null
|
||||||
|
|
||||||
const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => {
|
const oneClickCustomName: (type: OneClickType) => string | undefined = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case OneClickType.SAML:
|
case OneClickType.SAML:
|
||||||
return customAuthNames.saml;
|
return customAuthNames.saml
|
||||||
case OneClickType.OAUTH2:
|
case OneClickType.OAUTH2:
|
||||||
return customAuthNames.oauth2;
|
return customAuthNames.oauth2
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userLoginState === LoginStatus.ok) {
|
|
||||||
// TODO Redirect to previous page?
|
|
||||||
return (
|
|
||||||
<Redirect to='/history' />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userLoginState === LoginStatus.ok) {
|
||||||
|
// TODO Redirect to previous page?
|
||||||
return (
|
return (
|
||||||
<div className="my-3">
|
<Redirect to='/history'/>
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
|
|
||||||
export interface PageItemProps {
|
export interface PageItemProps {
|
||||||
onClick: (index: number) => void
|
onClick: (index: number) => void
|
||||||
index: number
|
index: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PagerItem: React.FC<PageItemProps> = ({ index, onClick }) => {
|
||||||
export const PagerItem: React.FC<PageItemProps> = ({index, onClick}) => {
|
return (
|
||||||
return (
|
<li className="page-item">
|
||||||
<li className="page-item">
|
<span className="page-link" role="button" onClick={() => onClick(index)}>
|
||||||
<span className="page-link" role="button" onClick={() => onClick(index)}>
|
{index + 1}
|
||||||
{index + 1}
|
</span>
|
||||||
</span>
|
</li>
|
||||||
</li>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {Fragment, useEffect, useState} from "react";
|
import React, { Fragment, useEffect, useState } from 'react'
|
||||||
import {Pagination} from "react-bootstrap";
|
import { Pagination } from 'react-bootstrap'
|
||||||
import {PagerItem} from "./pager-item";
|
import { PagerItem } from './pager-item'
|
||||||
|
|
||||||
export interface PaginationProps {
|
export interface PaginationProps {
|
||||||
numberOfPageButtonsToShowAfterAndBeforeCurrent: number
|
numberOfPageButtonsToShowAfterAndBeforeCurrent: number
|
||||||
|
@ -8,75 +8,75 @@ export interface PaginationProps {
|
||||||
lastPageIndex: number
|
lastPageIndex: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PagerPagination: React.FC<PaginationProps> = ({numberOfPageButtonsToShowAfterAndBeforeCurrent, onPageChange, lastPageIndex}) => {
|
export const PagerPagination: React.FC<PaginationProps> = ({ numberOfPageButtonsToShowAfterAndBeforeCurrent, onPageChange, lastPageIndex }) => {
|
||||||
if (numberOfPageButtonsToShowAfterAndBeforeCurrent % 2 !== 0) {
|
if (numberOfPageButtonsToShowAfterAndBeforeCurrent % 2 !== 0) {
|
||||||
throw new Error("number of pages to show must be even!")
|
throw new Error('number of pages to show must be even!')
|
||||||
}
|
}
|
||||||
|
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0)
|
||||||
const correctedPageIndex = Math.min(pageIndex, lastPageIndex);
|
const correctedPageIndex = Math.min(pageIndex, lastPageIndex)
|
||||||
const wantedUpperPageIndex = correctedPageIndex + numberOfPageButtonsToShowAfterAndBeforeCurrent;
|
const wantedUpperPageIndex = correctedPageIndex + numberOfPageButtonsToShowAfterAndBeforeCurrent
|
||||||
const wantedLowerPageIndex = correctedPageIndex - numberOfPageButtonsToShowAfterAndBeforeCurrent;
|
const wantedLowerPageIndex = correctedPageIndex - numberOfPageButtonsToShowAfterAndBeforeCurrent
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onPageChange(pageIndex)
|
onPageChange(pageIndex)
|
||||||
}, [onPageChange, pageIndex])
|
}, [onPageChange, pageIndex])
|
||||||
|
|
||||||
const correctedLowerPageIndex =
|
const correctedLowerPageIndex =
|
||||||
Math.min(
|
Math.min(
|
||||||
Math.max(
|
|
||||||
Math.min(
|
|
||||||
wantedLowerPageIndex,
|
|
||||||
wantedLowerPageIndex + lastPageIndex - wantedUpperPageIndex
|
|
||||||
),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
lastPageIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
const correctedUpperPageIndex =
|
|
||||||
Math.max(
|
Math.max(
|
||||||
Math.min(
|
Math.min(
|
||||||
Math.max(
|
wantedLowerPageIndex,
|
||||||
wantedUpperPageIndex,
|
wantedLowerPageIndex + lastPageIndex - wantedUpperPageIndex
|
||||||
wantedUpperPageIndex - wantedLowerPageIndex
|
),
|
||||||
),
|
0
|
||||||
lastPageIndex
|
),
|
||||||
),
|
lastPageIndex
|
||||||
0
|
)
|
||||||
);
|
|
||||||
|
|
||||||
const paginationItemsBefore = Array.from(new Array(correctedPageIndex - correctedLowerPageIndex)).map((k, index) => {
|
const correctedUpperPageIndex =
|
||||||
const itemIndex = correctedLowerPageIndex + index;
|
Math.max(
|
||||||
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
Math.min(
|
||||||
});
|
Math.max(
|
||||||
|
wantedUpperPageIndex,
|
||||||
|
wantedUpperPageIndex - wantedLowerPageIndex
|
||||||
|
),
|
||||||
|
lastPageIndex
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
const paginationItemsAfter = Array.from(new Array(correctedUpperPageIndex - correctedPageIndex)).map((k, index) => {
|
const paginationItemsBefore = Array.from(new Array(correctedPageIndex - correctedLowerPageIndex)).map((k, index) => {
|
||||||
const itemIndex = correctedPageIndex + index + 1;
|
const itemIndex = correctedLowerPageIndex + index
|
||||||
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
||||||
});
|
})
|
||||||
|
|
||||||
return (
|
const paginationItemsAfter = Array.from(new Array(correctedUpperPageIndex - correctedPageIndex)).map((k, index) => {
|
||||||
<Pagination>
|
const itemIndex = correctedPageIndex + index + 1
|
||||||
{
|
return <PagerItem key={itemIndex} index={itemIndex} onClick={setPageIndex}/>
|
||||||
correctedLowerPageIndex > 0 ?
|
})
|
||||||
<Fragment>
|
|
||||||
<PagerItem key={0} index={0} onClick={setPageIndex}/>
|
return (
|
||||||
<Pagination.Ellipsis disabled/>
|
<Pagination>
|
||||||
</Fragment>
|
{
|
||||||
: null
|
correctedLowerPageIndex > 0
|
||||||
}
|
? <Fragment>
|
||||||
{paginationItemsBefore}
|
<PagerItem key={0} index={0} onClick={setPageIndex}/>
|
||||||
<Pagination.Item active>{correctedPageIndex + 1}</Pagination.Item>
|
<Pagination.Ellipsis disabled/>
|
||||||
{paginationItemsAfter}
|
</Fragment>
|
||||||
{
|
: null
|
||||||
correctedUpperPageIndex < lastPageIndex ?
|
}
|
||||||
<Fragment>
|
{paginationItemsBefore}
|
||||||
<Pagination.Ellipsis disabled/>
|
<Pagination.Item active>{correctedPageIndex + 1}</Pagination.Item>
|
||||||
<PagerItem key={lastPageIndex} index={lastPageIndex} onClick={setPageIndex}/>
|
{paginationItemsAfter}
|
||||||
</Fragment>
|
{
|
||||||
: null
|
correctedUpperPageIndex < lastPageIndex
|
||||||
}
|
? <Fragment>
|
||||||
</Pagination>
|
<Pagination.Ellipsis disabled/>
|
||||||
);
|
<PagerItem key={lastPageIndex} index={lastPageIndex} onClick={setPageIndex}/>
|
||||||
|
</Fragment>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Pagination>
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {Fragment, useEffect} from "react";
|
import React, { Fragment, useEffect } from 'react'
|
||||||
|
|
||||||
export interface PagerPageProps {
|
export interface PagerPageProps {
|
||||||
pageIndex: number
|
pageIndex: number
|
||||||
|
@ -6,19 +6,18 @@ export interface PagerPageProps {
|
||||||
onLastPageIndexChange: (lastPageIndex: number) => void
|
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(() => {
|
return <Fragment>
|
||||||
const lastPageIndex = Math.ceil(React.Children.count(children) / numberOfElementsPerPage) - 1;
|
{
|
||||||
onLastPageIndexChange(lastPageIndex)
|
React.Children.toArray(children).filter((value, index) => {
|
||||||
}, [children, numberOfElementsPerPage, onLastPageIndexChange])
|
const pageOfElement = Math.floor((index) / numberOfElementsPerPage)
|
||||||
|
return (pageOfElement === pageIndex)
|
||||||
return <Fragment>
|
})
|
||||||
{
|
}
|
||||||
React.Children.toArray(children).filter((value, index) => {
|
</Fragment>
|
||||||
const pageOfElement = Math.floor((index) / numberOfElementsPerPage);
|
|
||||||
return (pageOfElement === pageIndex);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
import { ButtonProps } from 'react-bootstrap'
|
||||||
import {ButtonProps} from "react-bootstrap";
|
import { IconProp } from '../../utils/iconProp'
|
||||||
import {IconButton} from "../icon-button/icon-button";
|
import { IconButton } from '../icon-button/icon-button'
|
||||||
|
|
||||||
export enum SortModeEnum {
|
export enum SortModeEnum {
|
||||||
up = 1,
|
up = 1,
|
||||||
|
@ -10,15 +10,15 @@ export enum SortModeEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIcon = (direction: SortModeEnum): IconProp => {
|
const getIcon = (direction: SortModeEnum): IconProp => {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
default:
|
default:
|
||||||
case SortModeEnum.no:
|
case SortModeEnum.no:
|
||||||
return "sort";
|
return 'sort'
|
||||||
case SortModeEnum.up:
|
case SortModeEnum.up:
|
||||||
return "sort-up";
|
return 'sort-up'
|
||||||
case SortModeEnum.down:
|
case SortModeEnum.down:
|
||||||
return "sort-down";
|
return 'sort-down'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SortButtonProps extends ButtonProps {
|
export interface SortButtonProps extends ButtonProps {
|
||||||
|
@ -27,21 +27,21 @@ export interface SortButtonProps extends ButtonProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleDirection = (direction: SortModeEnum) => {
|
const toggleDirection = (direction: SortModeEnum) => {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case SortModeEnum.no:
|
case SortModeEnum.no:
|
||||||
return SortModeEnum.up;
|
return SortModeEnum.up
|
||||||
case SortModeEnum.up:
|
case SortModeEnum.up:
|
||||||
return SortModeEnum.down;
|
return SortModeEnum.down
|
||||||
default:
|
default:
|
||||||
case SortModeEnum.down:
|
case SortModeEnum.down:
|
||||||
return SortModeEnum.no;
|
return SortModeEnum.no
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SortButton: React.FC<SortButtonProps> = ({children, variant, onChange, direction}) => {
|
export const SortButton: React.FC<SortButtonProps> = ({ children, variant, onChange, direction }) => {
|
||||||
const toggleSort = () => {
|
const toggleSort = () => {
|
||||||
onChange(toggleDirection(direction));
|
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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import {BrowserRouter as Router} from 'react-router-dom'
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
import * as serviceWorker from './service-worker';
|
import * as serviceWorker from './service-worker'
|
||||||
import {Landing} from "./components/landing/layout";
|
import { Landing } from './components/landing/layout'
|
||||||
import {ApplicationLoader} from "./components/application-loader/application-loader";
|
import { ApplicationLoader } from './components/application-loader/application-loader'
|
||||||
import {Provider} from "react-redux";
|
import { Provider } from 'react-redux'
|
||||||
import {store} from "./utils/store";
|
import { store } from './utils/store'
|
||||||
import {setUp} from "./initializers";
|
import { setUp } from './initializers'
|
||||||
|
|
||||||
const initTasks = setUp();
|
const initTasks = setUp()
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ApplicationLoader initTasks={initTasks}>
|
<ApplicationLoader initTasks={initTasks}>
|
||||||
<Router>
|
<Router>
|
||||||
<Landing/>
|
<Landing/>
|
||||||
</Router>
|
</Router>
|
||||||
</ApplicationLoader>
|
</ApplicationLoader>
|
||||||
</Provider>
|
</Provider>
|
||||||
, document.getElementById('root')
|
, document.getElementById('root')
|
||||||
);
|
)
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
serviceWorker.unregister();
|
serviceWorker.unregister()
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import {getBackendConfig, getFrontendConfig} from "../api/config";
|
import { getBackendConfig, getFrontendConfig } from '../api/config'
|
||||||
import {setFrontendConfig} from "../redux/frontend-config/methods";
|
import { setFrontendConfig } from '../redux/frontend-config/methods'
|
||||||
import {setBackendConfig} from "../redux/backend-config/methods";
|
import { setBackendConfig } from '../redux/backend-config/methods'
|
||||||
import {getAndSetUser} from "../utils/apiUtils";
|
import { getAndSetUser } from '../utils/apiUtils'
|
||||||
|
|
||||||
export async function loadAllConfig() {
|
export const loadAllConfig: () => Promise<void> = async () => {
|
||||||
const frontendConfig = await getFrontendConfig();
|
const frontendConfig = await getFrontendConfig()
|
||||||
if (!frontendConfig) {
|
if (!frontendConfig) {
|
||||||
return Promise.reject("Frontend config empty!");
|
return Promise.reject(new Error('Frontend config empty!'))
|
||||||
}
|
}
|
||||||
setFrontendConfig(frontendConfig);
|
setFrontendConfig(frontendConfig)
|
||||||
|
|
||||||
const backendConfig = await getBackendConfig()
|
const backendConfig = await getBackendConfig()
|
||||||
if (!backendConfig) {
|
if (!backendConfig) {
|
||||||
return Promise.reject("Backend config empty!");
|
return Promise.reject(new Error('Backend config empty!'))
|
||||||
}
|
}
|
||||||
setBackendConfig(backendConfig)
|
setBackendConfig(backendConfig)
|
||||||
|
|
||||||
await getAndSetUser();
|
await getAndSetUser()
|
||||||
}
|
}
|
|
@ -1,41 +1,41 @@
|
||||||
import {library} from "@fortawesome/fontawesome-svg-core";
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faAddressCard,
|
faAddressCard,
|
||||||
faBolt,
|
faBolt,
|
||||||
faChartBar,
|
faChartBar,
|
||||||
faClock,
|
faClock,
|
||||||
faCloudDownloadAlt,
|
faCloudDownloadAlt,
|
||||||
faComment,
|
faComment,
|
||||||
faDownload,
|
faDownload,
|
||||||
faFileAlt,
|
faFileAlt,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSignOutAlt,
|
faSignOutAlt,
|
||||||
faSort,
|
faSort,
|
||||||
faSortDown,
|
faSortDown,
|
||||||
faSortUp,
|
faSortUp,
|
||||||
faSync,
|
faSync,
|
||||||
faThumbtack,
|
faThumbtack,
|
||||||
faTimes,
|
faTimes,
|
||||||
faTrash,
|
faTrash,
|
||||||
faTv,
|
faTv,
|
||||||
faUpload,
|
faUpload,
|
||||||
faUsers,
|
faUsers
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {
|
import {
|
||||||
faDiscourse,
|
faDiscourse,
|
||||||
faDropbox,
|
faDropbox,
|
||||||
faFacebook,
|
faFacebook,
|
||||||
faGithub,
|
faGithub,
|
||||||
faGitlab,
|
faGitlab,
|
||||||
faGoogle,
|
faGoogle,
|
||||||
faMastodon,
|
faMastodon,
|
||||||
faTwitter
|
faTwitter
|
||||||
} from "@fortawesome/free-brands-svg-icons";
|
} from '@fortawesome/free-brands-svg-icons'
|
||||||
|
|
||||||
export function setUpFontAwesome() {
|
export const setUpFontAwesome: () => void = () => {
|
||||||
library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt,
|
library.add(faBolt, faPlus, faChartBar, faTv, faFileAlt, faCloudDownloadAlt,
|
||||||
faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe,
|
faTrash, faSignOutAlt, faComment, faDiscourse, faMastodon, faGlobe,
|
||||||
faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook,
|
faThumbtack, faClock, faTimes, faGithub, faGitlab, faGoogle, faFacebook,
|
||||||
faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown)
|
faDropbox, faTwitter, faUsers, faAddressCard, faSort, faDownload, faUpload, faTrash, faSync, faSortUp, faSortDown)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,52 @@
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next'
|
||||||
import Backend from 'i18next-http-backend';
|
import Backend from 'i18next-http-backend'
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||||
import {initReactI18next} from 'react-i18next';
|
import { initReactI18next } from 'react-i18next'
|
||||||
import moment from "moment";
|
import moment from 'moment'
|
||||||
import "moment/locale/ar";
|
import 'moment/locale/ar'
|
||||||
import "moment/locale/ca";
|
import 'moment/locale/ca'
|
||||||
import "moment/locale/cs";
|
import 'moment/locale/cs'
|
||||||
import "moment/locale/da";
|
import 'moment/locale/da'
|
||||||
import "moment/locale/de";
|
import 'moment/locale/de'
|
||||||
import "moment/locale/el";
|
import 'moment/locale/el'
|
||||||
import "moment/locale/eo";
|
import 'moment/locale/eo'
|
||||||
import "moment/locale/es";
|
import 'moment/locale/es'
|
||||||
import "moment/locale/fr";
|
import 'moment/locale/fr'
|
||||||
import "moment/locale/hi";
|
import 'moment/locale/hi'
|
||||||
import "moment/locale/hr";
|
import 'moment/locale/hr'
|
||||||
import "moment/locale/id";
|
import 'moment/locale/id'
|
||||||
import "moment/locale/it";
|
import 'moment/locale/it'
|
||||||
import "moment/locale/ja";
|
import 'moment/locale/ja'
|
||||||
import "moment/locale/ko";
|
import 'moment/locale/ko'
|
||||||
import "moment/locale/nl";
|
import 'moment/locale/nl'
|
||||||
import "moment/locale/pl";
|
import 'moment/locale/pl'
|
||||||
import "moment/locale/pt";
|
import 'moment/locale/pt'
|
||||||
import "moment/locale/ru";
|
import 'moment/locale/ru'
|
||||||
import "moment/locale/sk";
|
import 'moment/locale/sk'
|
||||||
import "moment/locale/sr";
|
import 'moment/locale/sr'
|
||||||
import "moment/locale/sv";
|
import 'moment/locale/sv'
|
||||||
import "moment/locale/tr";
|
import 'moment/locale/tr'
|
||||||
import "moment/locale/uk";
|
import 'moment/locale/uk'
|
||||||
import "moment/locale/vi";
|
import 'moment/locale/vi'
|
||||||
import "moment/locale/zh-cn";
|
import 'moment/locale/zh-cn'
|
||||||
import "moment/locale/zh-tw";
|
import 'moment/locale/zh-tw'
|
||||||
|
|
||||||
export async function setUpI18n() {
|
export const setUpI18n: () => Promise<void> = async () => {
|
||||||
await i18n
|
await i18n
|
||||||
.use(Backend)
|
.use(Backend)
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
debug: true,
|
debug: true,
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: '/locales/{{lng}}.json',
|
loadPath: '/locales/{{lng}}.json'
|
||||||
},
|
},
|
||||||
|
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // not needed for react as it escapes by default
|
escapeValue: false // not needed for react as it escapes by default
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
moment.locale(i18n.language);
|
moment.locale(i18n.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import {setUpFontAwesome} from "./fontAwesome";
|
import { setUpFontAwesome } from './fontAwesome'
|
||||||
import {setUpI18n} from "./i18n";
|
import { setUpI18n } from './i18n'
|
||||||
import {loadAllConfig} from "./configLoader";
|
import { loadAllConfig } from './configLoader'
|
||||||
|
|
||||||
function customDelay() {
|
const customDelay: () => Promise<void> = async () => {
|
||||||
if (!!window.localStorage.getItem("customDelay")) {
|
if (window.localStorage.getItem('customDelay')) {
|
||||||
return new Promise((resolve => setTimeout(resolve, 5000)));
|
return new Promise(resolve => setTimeout(resolve, 5000))
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUp() {
|
export const setUp: () => Promise<void>[] = () => {
|
||||||
setUpFontAwesome();
|
setUpFontAwesome()
|
||||||
return [setUpI18n(), loadAllConfig(), customDelay()]
|
return [setUpI18n(), loadAllConfig(), customDelay()]
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import {BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction} from "./types";
|
import { BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction } from './types'
|
||||||
import {store} from "../../utils/store";
|
import { store } from '../../utils/store'
|
||||||
|
|
||||||
export const setBackendConfig = (state: BackendConfigState) => {
|
export const setBackendConfig: (state: BackendConfigState) => void = (state: BackendConfigState) => {
|
||||||
const action: SetBackendConfigAction = {
|
const action: SetBackendConfigAction = {
|
||||||
type: SET_BACKEND_CONFIG_ACTION_TYPE,
|
type: SET_BACKEND_CONFIG_ACTION_TYPE,
|
||||||
payload: {
|
payload: {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
store.dispatch(action)
|
store.dispatch(action)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
import {Reducer} from 'redux';
|
import { Reducer } from 'redux'
|
||||||
import {BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE} from './types';
|
import { BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE } from './types'
|
||||||
|
|
||||||
export const initialState: BackendConfigState = {
|
export const initialState: BackendConfigState = {
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
authProviders: {
|
authProviders: {
|
||||||
facebook: false,
|
facebook: false,
|
||||||
github: false,
|
github: false,
|
||||||
twitter: false,
|
twitter: false,
|
||||||
gitlab: false,
|
gitlab: false,
|
||||||
dropbox: false,
|
dropbox: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
google: false,
|
google: false,
|
||||||
saml: false,
|
saml: false,
|
||||||
oauth2: false,
|
oauth2: false,
|
||||||
email: false,
|
email: false,
|
||||||
openid: false
|
openid: false
|
||||||
},
|
},
|
||||||
customAuthNames: {
|
customAuthNames: {
|
||||||
ldap: "",
|
ldap: '',
|
||||||
oauth2: "",
|
oauth2: '',
|
||||||
saml: ""
|
saml: ''
|
||||||
},
|
},
|
||||||
specialLinks: {
|
specialLinks: {
|
||||||
privacy: "",
|
privacy: '',
|
||||||
termsOfUse: "",
|
termsOfUse: '',
|
||||||
imprint: "",
|
imprint: ''
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const BackendConfigReducer: Reducer<BackendConfigState, BackendConfigActions> = (state: BackendConfigState = initialState, action: BackendConfigActions) => {
|
export const BackendConfigReducer: Reducer<BackendConfigState, BackendConfigActions> = (state: BackendConfigState = initialState, action: BackendConfigActions) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_BACKEND_CONFIG_ACTION_TYPE:
|
case SET_BACKEND_CONFIG_ACTION_TYPE:
|
||||||
return action.payload.state;
|
return action.payload.state
|
||||||
default:
|
default:
|
||||||
return state;
|
return state
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -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 {
|
export interface BackendConfigState {
|
||||||
allowAnonymous: boolean,
|
allowAnonymous: boolean,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction} from "./types";
|
import { FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction } from './types'
|
||||||
import {store} from "../../utils/store";
|
import { store } from '../../utils/store'
|
||||||
|
|
||||||
export const setFrontendConfig = (state: FrontendConfigState) => {
|
export const setFrontendConfig: (state: FrontendConfigState) => void = (state: FrontendConfigState) => {
|
||||||
const action: SetFrontendConfigAction = {
|
const action: SetFrontendConfigAction = {
|
||||||
type: SET_FRONTEND_CONFIG_ACTION_TYPE,
|
type: SET_FRONTEND_CONFIG_ACTION_TYPE,
|
||||||
payload: {
|
payload: {
|
||||||
state
|
state
|
||||||
}
|
|
||||||
}
|
}
|
||||||
store.dispatch(action);
|
}
|
||||||
|
store.dispatch(action)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import {Reducer} from 'redux';
|
import { Reducer } from 'redux'
|
||||||
import {FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE} from './types';
|
import { FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE } from './types'
|
||||||
|
|
||||||
export const initialState: FrontendConfigState = {
|
export const initialState: FrontendConfigState = {
|
||||||
backendUrl: ""
|
backendUrl: ''
|
||||||
};
|
}
|
||||||
|
|
||||||
export const FrontendConfigReducer: Reducer<FrontendConfigState, FrontendConfigActions> = (state: FrontendConfigState = initialState, action: FrontendConfigActions) => {
|
export const FrontendConfigReducer: Reducer<FrontendConfigState, FrontendConfigActions> = (state: FrontendConfigState = initialState, action: FrontendConfigActions) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_FRONTEND_CONFIG_ACTION_TYPE:
|
case SET_FRONTEND_CONFIG_ACTION_TYPE:
|
||||||
return action.payload.state;
|
return action.payload.state
|
||||||
default:
|
default:
|
||||||
return state;
|
return state
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -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 {
|
export interface SetFrontendConfigAction extends Action {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {combineReducers, Reducer} from 'redux';
|
import { combineReducers, Reducer } from 'redux'
|
||||||
import {UserState} from "./user/types";
|
import { UserState } from './user/types'
|
||||||
import {UserReducer} from "./user/reducers";
|
import { UserReducer } from './user/reducers'
|
||||||
import {ModalShowReducer} from "./modal/reducers";
|
import { ModalShowReducer } from './modal/reducers'
|
||||||
import {ModalShowState} from "./modal/types";
|
import { ModalShowState } from './modal/types'
|
||||||
import {BackendConfigState} from "./backend-config/types";
|
import { BackendConfigState } from './backend-config/types'
|
||||||
import {FrontendConfigState} from "./frontend-config/types";
|
import { FrontendConfigState } from './frontend-config/types'
|
||||||
import {BackendConfigReducer} from "./backend-config/reducers";
|
import { BackendConfigReducer } from './backend-config/reducers'
|
||||||
import {FrontendConfigReducer} from "./frontend-config/reducers";
|
import { FrontendConfigReducer } from './frontend-config/reducers'
|
||||||
|
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
user: UserState;
|
user: UserState;
|
||||||
|
@ -16,8 +16,8 @@ export interface ApplicationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||||
user: UserReducer,
|
user: UserReducer,
|
||||||
modalShow: ModalShowReducer,
|
modalShow: ModalShowReducer,
|
||||||
backendConfig: BackendConfigReducer,
|
backendConfig: BackendConfigReducer,
|
||||||
frontendConfig: FrontendConfigReducer
|
frontendConfig: FrontendConfigReducer
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {ActionCreator} from "redux";
|
import { ActionCreator } from 'redux'
|
||||||
import {SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, SetHistoryDeleteModalShowAction} from "./types";
|
import { SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, SetHistoryDeleteModalShowAction } from './types'
|
||||||
|
|
||||||
export const setSignInModalShow: ActionCreator<SetHistoryDeleteModalShowAction> = (historyDelete: boolean) => ({
|
export const setSignInModalShow: ActionCreator<SetHistoryDeleteModalShowAction> = (historyDelete: boolean) => ({
|
||||||
type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE,
|
type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE,
|
||||||
payload: historyDelete,
|
payload: historyDelete
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import {Reducer} from 'redux';
|
import { Reducer } from 'redux'
|
||||||
import {ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from './types';
|
import { ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE } from './types'
|
||||||
|
|
||||||
export const initialState: ModalShowState = {
|
export const initialState: ModalShowState = {
|
||||||
historyDelete: false
|
historyDelete: false
|
||||||
};
|
}
|
||||||
|
|
||||||
export const ModalShowReducer: Reducer<ModalShowState, ModalShowActions> = (state: ModalShowState = initialState, action: ModalShowActions) => {
|
export const ModalShowReducer: Reducer<ModalShowState, ModalShowActions> = (state: ModalShowState = initialState, action: ModalShowActions) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE:
|
case SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
historyDelete: action.payload
|
historyDelete: action.payload
|
||||||
};
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -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 {
|
export interface ModalShowState {
|
||||||
historyDelete: boolean
|
historyDelete: boolean
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import {CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState} from "./types";
|
import { CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState } from './types'
|
||||||
import {store} from "../../utils/store";
|
import { store } from '../../utils/store'
|
||||||
|
|
||||||
export const setUser = (state: UserState) => {
|
export const setUser: (state: UserState) => void = (state: UserState) => {
|
||||||
const action: SetUserAction = {
|
const action: SetUserAction = {
|
||||||
type: SET_USER_ACTION_TYPE,
|
type: SET_USER_ACTION_TYPE,
|
||||||
payload: {
|
payload: {
|
||||||
state
|
state
|
||||||
}
|
|
||||||
}
|
}
|
||||||
store.dispatch(action);
|
}
|
||||||
|
store.dispatch(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearUser = () => {
|
export const clearUser: () => void = () => {
|
||||||
const action: ClearUserAction = {
|
const action: ClearUserAction = {
|
||||||
type: CLEAR_USER_ACTION_TYPE,
|
type: CLEAR_USER_ACTION_TYPE,
|
||||||
payload: {},
|
payload: null
|
||||||
}
|
}
|
||||||
store.dispatch(action);
|
store.dispatch(action)
|
||||||
}
|
}
|
|
@ -1,27 +1,20 @@
|
||||||
import {Reducer} from 'redux';
|
import { Reducer } from 'redux'
|
||||||
import {
|
import { CLEAR_USER_ACTION_TYPE, LoginStatus, SET_USER_ACTION_TYPE, SetUserAction, UserActions, UserState } from './types'
|
||||||
CLEAR_USER_ACTION_TYPE,
|
|
||||||
LoginStatus,
|
|
||||||
SET_USER_ACTION_TYPE,
|
|
||||||
SetUserAction,
|
|
||||||
UserActions,
|
|
||||||
UserState
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
export const initialState: UserState = {
|
export const initialState: UserState = {
|
||||||
id: "",
|
id: '',
|
||||||
name: "",
|
name: '',
|
||||||
photo: "",
|
photo: '',
|
||||||
status: LoginStatus.forbidden
|
status: LoginStatus.forbidden
|
||||||
};
|
}
|
||||||
|
|
||||||
export const UserReducer: Reducer<UserState, UserActions> = (state: UserState = initialState, action: UserActions) => {
|
export const UserReducer: Reducer<UserState, UserActions> = (state: UserState = initialState, action: UserActions) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_USER_ACTION_TYPE:
|
case SET_USER_ACTION_TYPE:
|
||||||
return (action as SetUserAction).payload.state;
|
return (action as SetUserAction).payload.state
|
||||||
case CLEAR_USER_ACTION_TYPE:
|
case CLEAR_USER_ACTION_TYPE:
|
||||||
return initialState;
|
return initialState
|
||||||
default:
|
default:
|
||||||
return state;
|
return state
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Action} from "redux";
|
import { Action } from 'redux'
|
||||||
|
|
||||||
export const SET_USER_ACTION_TYPE = 'user/set';
|
export const SET_USER_ACTION_TYPE = 'user/set'
|
||||||
export const CLEAR_USER_ACTION_TYPE = 'user/clear';
|
export const CLEAR_USER_ACTION_TYPE = 'user/clear'
|
||||||
|
|
||||||
export interface SetUserAction extends Action {
|
export interface SetUserAction extends Action {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -12,7 +12,7 @@ export interface SetUserAction extends Action {
|
||||||
|
|
||||||
export interface ClearUserAction extends Action {
|
export interface ClearUserAction extends Action {
|
||||||
type: string;
|
type: string;
|
||||||
payload: {};
|
payload: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
|
@ -23,8 +23,8 @@ export interface UserState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LoginStatus {
|
export enum LoginStatus {
|
||||||
forbidden = "forbidden",
|
forbidden = 'forbidden',
|
||||||
ok = "ok"
|
ok = 'ok'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserActions = SetUserAction | ClearUserAction;
|
export type UserActions = SetUserAction | ClearUserAction;
|
||||||
|
|
|
@ -15,61 +15,50 @@ const isLocalhost = Boolean(
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
new RegExp(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/).exec(window.location.hostname)
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
)
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||||
onUpdate?: (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) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(
|
const publicUrl = new URL(
|
||||||
process.env.PUBLIC_URL,
|
process.env.PUBLIC_URL,
|
||||||
window.location.href
|
window.location.href
|
||||||
);
|
)
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different 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
|
// 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
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
checkValidServiceWorker(swUrl, config);
|
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'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Is not localhost. Just register service worker
|
// 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
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then(registration => {
|
.then(registration => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing
|
||||||
if (installingWorker == null) {
|
if (installingWorker == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
installingWorker.onstatechange = () => {
|
installingWorker.onstatechange = () => {
|
||||||
if (installingWorker.state === 'installed') {
|
if (installingWorker.state === 'installed') {
|
||||||
|
@ -80,70 +69,70 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
'New content is available and will be used when all ' +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
);
|
)
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onUpdate) {
|
if (config && config.onUpdate) {
|
||||||
config.onUpdate(registration);
|
config.onUpdate(registration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "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
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
config.onSuccess(registration);
|
config.onSuccess(registration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { 'Service-Worker': 'script' }
|
headers: { 'Service-Worker': 'script' }
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// 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 (
|
if (
|
||||||
response.status === 404 ||
|
response.status === 404 ||
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
) {
|
) {
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
registration.unregister().then(() => {
|
return registration.unregister().then(() => {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
});
|
})
|
||||||
});
|
}).catch(() => console.log('Service worker not ready'))
|
||||||
} else {
|
} else {
|
||||||
// Service worker found. Proceed as normal.
|
// Service worker found. Proceed as normal.
|
||||||
registerValidSW(swUrl, config);
|
registerValidSW(swUrl, config)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
'No internet connection found. App is running in offline mode.'
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregister() {
|
export function unregister ():void {
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then(registration => {
|
.then(registration => {
|
||||||
registration.unregister();
|
return registration.unregister()
|
||||||
|
})
|
||||||
|
.catch((error:Error) => {
|
||||||
|
console.error(error.message)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
|
||||||
console.error(error.message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
|
|
|
@ -1,27 +1,22 @@
|
||||||
import {getMe} from "../api/user";
|
import { getMe } from '../api/user'
|
||||||
import {LoginStatus} from "../redux/user/types";
|
import { LoginStatus } from '../redux/user/types'
|
||||||
import {setUser} from "../redux/user/methods";
|
import { setUser } from '../redux/user/methods'
|
||||||
import {store} from "./store";
|
import { store } from './store'
|
||||||
|
|
||||||
export const getAndSetUser = async () => {
|
export const getAndSetUser: () => (Promise<void>) = async () => {
|
||||||
const meResponse = await getMe();
|
const me = await getMe()
|
||||||
expectResponseCode(meResponse);
|
setUser({
|
||||||
const me = await meResponse.json();
|
status: LoginStatus.ok,
|
||||||
if (!me) {
|
id: me.id,
|
||||||
return;
|
name: me.name,
|
||||||
}
|
photo: me.photo
|
||||||
setUser({
|
})
|
||||||
status: LoginStatus.ok,
|
|
||||||
id: me.id,
|
|
||||||
name: me.name,
|
|
||||||
photo: me.photo,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBackendUrl = () => {
|
export const getBackendUrl: (() => string) = () => {
|
||||||
return store.getState().frontendConfig.backendUrl;
|
return store.getState().frontendConfig.backendUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
export const expectResponseCode = (response: Response, code: number = 200) => {
|
export const expectResponseCode: ((response: Response, code?: number) => boolean) = (response, code = 200) => {
|
||||||
return (response.status !== code);
|
return response.status !== code
|
||||||
}
|
}
|
|
@ -1,61 +1,61 @@
|
||||||
import {HistoryEntry} from "../components/landing/pages/history/history";
|
import { HistoryEntry } from '../components/landing/pages/history/history'
|
||||||
import moment from "moment";
|
import moment from 'moment'
|
||||||
import {HistoryToolbarState} from "../components/landing/pages/history/history-toolbar/history-toolbar";
|
import { HistoryToolbarState } from '../components/landing/pages/history/history-toolbar/history-toolbar'
|
||||||
import {SortModeEnum} from "../components/sort-button/sort-button";
|
import { SortModeEnum } from '../components/sort-button/sort-button'
|
||||||
|
|
||||||
export function sortAndFilterEntries(entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] {
|
export function sortAndFilterEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] {
|
||||||
return sortEntries(filterByKeywordSearch(filterBySelectedTags(entries, viewState.selectedTags), viewState.keywordSearch), viewState);
|
return sortEntries(filterByKeywordSearch(filterBySelectedTags(entries, viewState.selectedTags), viewState.keywordSearch), viewState)
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterBySelectedTags(entries: HistoryEntry[], selectedTags: string[]): HistoryEntry[] {
|
function filterBySelectedTags (entries: HistoryEntry[], selectedTags: string[]): HistoryEntry[] {
|
||||||
return entries.filter(entry => {
|
return entries.filter(entry => {
|
||||||
return (selectedTags.length === 0 || arrayCommonCheck(entry.tags, selectedTags))
|
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 {
|
function filterByKeywordSearch (entries: HistoryEntry[], keywords: string): HistoryEntry[] {
|
||||||
const foundElement = array1.find((element1) =>
|
const searchTerm = keywords.toLowerCase()
|
||||||
array2.find((element2) =>
|
return entries.filter(entry => entry.title.toLowerCase().indexOf(searchTerm) !== -1)
|
||||||
element2 === element1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return !!foundElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByKeywordSearch(entries: HistoryEntry[], keywords: string): HistoryEntry[] {
|
function sortEntries (entries: HistoryEntry[], viewState: HistoryToolbarState): HistoryEntry[] {
|
||||||
const searchTerm = keywords.toLowerCase();
|
return entries.sort((firstEntry, secondEntry) => {
|
||||||
return entries.filter(entry => entry.title.toLowerCase().indexOf(searchTerm) !== -1);
|
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[] {
|
export function formatHistoryDate (date: Date): string {
|
||||||
return entries.sort((firstEntry, secondEntry) => {
|
return moment(date).format('llll')
|
||||||
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 interface OldHistoryEntry {
|
export interface OldHistoryEntry {
|
||||||
|
@ -66,23 +66,23 @@ export interface OldHistoryEntry {
|
||||||
pinned: boolean;
|
pinned: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadHistoryFromLocalStore(): HistoryEntry[] {
|
export function loadHistoryFromLocalStore (): HistoryEntry[] {
|
||||||
const historyJsonString = window.localStorage.getItem("history");
|
const historyJsonString = window.localStorage.getItem('history')
|
||||||
if (!historyJsonString) {
|
if (!historyJsonString) {
|
||||||
// if localStorage["history"] is empty we check the old localStorage["notehistory"]
|
// if localStorage["history"] is empty we check the old localStorage["notehistory"]
|
||||||
// and convert it to the new format
|
// and convert it to the new format
|
||||||
const oldHistoryJsonString = window.localStorage.getItem("notehistory")
|
const oldHistoryJsonString = window.localStorage.getItem('notehistory')
|
||||||
const oldHistory = !!oldHistoryJsonString ? JSON.parse(JSON.parse(oldHistoryJsonString)) : [];
|
const oldHistory = oldHistoryJsonString ? JSON.parse(JSON.parse(oldHistoryJsonString)) as OldHistoryEntry[] : []
|
||||||
return oldHistory.map((entry: OldHistoryEntry) => {
|
return oldHistory.map((entry: OldHistoryEntry) => {
|
||||||
return {
|
return {
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
title: entry.text,
|
title: entry.text,
|
||||||
lastVisited: moment(entry.time).toDate(),
|
lastVisited: moment(entry.time).toDate(),
|
||||||
tags: entry.tags,
|
tags: entry.tags,
|
||||||
pinned: entry.pinned,
|
pinned: entry.pinned
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(historyJsonString)
|
return JSON.parse(historyJsonString) as HistoryEntry[]
|
||||||
}
|
}
|
||||||
}
|
}
|
4
src/utils/iconProp.ts
Normal file
4
src/utils/iconProp.ts
Normal file
|
@ -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
|
|
@ -1,4 +1,4 @@
|
||||||
import {createStore} from "redux";
|
import { createStore } from 'redux'
|
||||||
import {allReducers} from "../redux";
|
import { allReducers } from '../redux'
|
||||||
|
|
||||||
export const store = createStore(allReducers);
|
export const store = createStore(allReducers)
|
||||||
|
|
126
yarn.lock
126
yarn.lock
|
@ -1728,6 +1728,17 @@
|
||||||
regexpp "^3.0.0"
|
regexpp "^3.0.0"
|
||||||
tsutils "^3.17.1"
|
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":
|
"@typescript-eslint/experimental-utils@2.31.0":
|
||||||
version "2.31.0"
|
version "2.31.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.31.0.tgz#a9ec514bf7fd5e5e82bc10dcb6a86d58baae9508"
|
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-scope "^5.0.0"
|
||||||
eslint-utils "^2.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":
|
"@typescript-eslint/parser@^2.10.0":
|
||||||
version "2.31.0"
|
version "2.31.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.31.0.tgz#beddd4e8efe64995108b229b2862cd5752d40d6f"
|
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"
|
"@typescript-eslint/typescript-estree" "2.31.0"
|
||||||
eslint-visitor-keys "^1.1.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":
|
"@typescript-eslint/typescript-estree@2.31.0":
|
||||||
version "2.31.0"
|
version "2.31.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.31.0.tgz#ac536c2d46672aa1f27ba0ec2140d53670635cfd"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.31.0.tgz#ac536c2d46672aa1f27ba0ec2140d53670635cfd"
|
||||||
|
@ -1761,6 +1792,19 @@
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
tsutils "^3.17.1"
|
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":
|
"@webassemblyjs/ast@1.8.5":
|
||||||
version "1.8.5"
|
version "1.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
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:
|
dependencies:
|
||||||
confusing-browser-globals "^1.0.9"
|
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:
|
eslint-import-resolver-node@^0.3.2:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404"
|
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"
|
debug "^2.6.9"
|
||||||
pkg-dir "^2.0.0"
|
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:
|
eslint-plugin-flowtype@4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451"
|
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:
|
dependencies:
|
||||||
lodash "^4.17.15"
|
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:
|
eslint-plugin-import@2.20.1:
|
||||||
version "2.20.1"
|
version "2.20.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3"
|
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"
|
read-pkg-up "^2.0.0"
|
||||||
resolve "^1.12.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"
|
version "6.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa"
|
||||||
integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==
|
integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==
|
||||||
|
@ -4290,6 +4373,23 @@ eslint-plugin-jsx-a11y@6.2.3:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
jsx-ast-utils "^2.2.1"
|
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:
|
eslint-plugin-react-hooks@^1.6.1:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04"
|
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"
|
string.prototype.matchall "^4.0.2"
|
||||||
xregexp "^4.3.0"
|
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:
|
eslint-scope@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
|
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"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
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:
|
immer@1.10.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
|
||||||
|
@ -9752,7 +9862,7 @@ resolve@1.15.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-parse "^1.0.6"
|
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"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
|
||||||
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
|
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"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
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"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
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"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
|
||||||
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
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:
|
semver@~5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
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"
|
astral-regex "^1.0.0"
|
||||||
strip-ansi "^5.2.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:
|
string-width@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||||
|
|
Loading…
Reference in a new issue