diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..444940d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,520 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +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 + +[*.java] +max_line_length = 100 +ij_continuation_indent_size = 4 +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = normal +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = normal +ij_java_assignment_wrap = normal +ij_java_binary_operation_sign_on_next_line = true +ij_java_binary_operation_wrap = normal +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 0 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 1 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = false +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = normal +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = false +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = normal +ij_java_extends_keyword_wrap = normal +ij_java_extends_list_wrap = normal +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = normal +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = always +ij_java_imports_layout = $*, |, *, |, * +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = true +ij_java_line_comment_at_first_column = false +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = normal +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = normal +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = normal +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = normal +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = true +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = true +ij_java_ternary_operation_wrap = normal +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +ij_java_throws_list_wrap = normal +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = normal +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = true + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.jspx, *.pom, *.rng, *.tagx, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.bash, *.sh, *.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false + +[{*.gant, *.gradle, *.groovy, *.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_long_lines = false + +[{*.har, *.json}] +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, *.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 + +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..1e1294d --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,18 @@ +name: CD + +on: + release: + types: + - published + +jobs: + cd: + uses: halo-sigs/reusable-workflows/.github/workflows/plugin-cd.yaml@v4 + permissions: + contents: write + with: + ui-path: "ui" + # 前端产物已提交进仓库,CI 无需安装 Node/pnpm(否则 setup-node 缓存保存会失败) + skip-node-setup: true + # 首次上架审核通过前,仅上传 GitHub Release 资产,不同步应用市场 + skip-appstore-release: true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a5e9d59 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + ci: + uses: halo-sigs/reusable-workflows/.github/workflows/plugin-ci.yaml@v4 + with: + ui-path: "ui" + # 前端产物已提交进仓库,CI 无需安装 Node/pnpm(否则 setup-node 缓存保存会失败) + skip-node-setup: true diff --git a/.gitignore b/.gitignore index a726e0a..c69f904 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,10 @@ build/ # ---- 前端(ui) ---- node_modules/ ui/node_modules/ -# console 产物由 `pnpm -C ui build` 生成,勿提交 -src/main/resources/console/ +# console 产物提交进仓库(CI skip-node-setup),由 `pnpm -C ui build` 更新,勿手改 + +# ---- 内部文档(不进公开仓库) ---- +AGENTS.md # ---- IDE ---- .idea/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 78ca3e3..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,39 +0,0 @@ -# AGENTS.md — sharelink(Halo 资源下载管理插件) - -## 项目概述 - -Halo 2.x(>=2.22,目标实例 Halo Pro 2.25.4)插件。管理文章中的资源下载链接:按资源的下载统计、邮箱验证、防附件直链、文章引用扫描。邮箱验证体系复制改造自 `~/Documents/Code/Ai/blog-comment`(plugin-comment-widget fork 的 emailcode/guard 包)。 - -## 结构 - -- `src/main/java/run/halo/sharelink/` - - `model/` 自定义 Extension(@GVK group `sharelink.halo.run`):DownloadResource(spec.slug 唯一索引)、DownloadRecord(spec.resourceSlug 索引)、VerifiedDownloader(name=SHA-256(归一化邮箱)) - - `emailcode/` 验证码:Guava 内存缓存、限流、NotificationCenter 发信(REASON_TYPE `sharelink-download-verification`,模板在 `resources/extensions/notification.yaml`) - - `verify/` 公开端点 `api.sharelink.halo.run/v1alpha1`(email-verify/-/send|/-/check)+ VerifiedEmailService(本插件 VerifiedDownloader ∪ 评论插件 VerifiedCommenter,后者运行时 Unstructured fetch,缺席自动降级) - - `download/` RouterFunction Bean:`GET /download/{slug}`(自包含 HTML 页)、`GET /download/{slug}/file`(一次性 token 核销 → 记录 → 流式下载);FileStreamer 本地附件直接读盘(`halo.work-dir`/attachments + `storage.halo.run/local-relative-path` 注解),外部存储回环 HTTP(带 `X-Sharelink-Internal` 头) - - `protect/UploadProtectFilter` AdditionalWebFilter:GET/HEAD `/upload/**` 命中受保护附件 → 404 - - `reference/PostReferenceService` 扫描已发布文章 releaseSnapshot 内容匹配 `/download/{slug}`,5min 缓存 - - `console/ConsoleEndpoint` `console.api.sharelink.halo.run/v1alpha1`:资源 CRUD(body 为扁平 spec)、记录分页/删除/CSV、references 查询/刷新 -- `src/main/resources/extensions/` settings.yaml、notification.yaml、role-templates.yaml(匿名放行公开 API;console view/manage 角色,`ui-permissions: plugin:sharelink:view/manage`) -- `ui/` console 前端:Vue 3 + @halo-dev/ui-shared + ui-plugin-bundler-kit(rsbuild),产物输出到 `src/main/resources/console/`(**构建前会覆盖该目录,勿手改**)。API 层 `ui/src/api/index.ts` 的 `normalizeResource()` 把后端扁平 ResourceVo 转成 Halo 风格结构 - -## 构建 / 部署 - -```bash -pnpm -C ui install && pnpm -C ui build -export JAVA_HOME=~/.gradle/jdks/jdk-21.0.12+8/Contents/Home # 本机无系统 JDK -./gradlew build -x test # build/libs/sharelink-.jar -``` - -关键经验(踩过的坑): -1. 改代码后发布必须递增 `gradle.properties` 的 version——console 静态资源按 `?version=` 缓存,不递增浏览器会用旧包。 -2. Console UI 升级插件会弹「插件已存在,是否升级?」确认框,不点确定不会真正替换 jar。 -3. 插件数据存 ExtensionStore(MySQL),卸载/重装插件不丢数据;但 Halo 的插件静态资源目录在重装时才重新解压。 -4. 计数在「token 核销 + 附件可流式输出」之后才写入,下载失败不计数;免验证资源按 slug+IP 内存窗口去重。 -5. UI 安装有两条路:①「远程下载」需要 Halo 服务器能反向访问本机 jar 服务(本机换网/IP 变化后会失败);②「本地上传」用 webbridge 时 CDP `setFileInputFiles` 被浏览器禁用,可用兜底方案:分块 base64 经 `evaluate` 推到页面 `window.__jarB64` → JS 构造 `File` + `DataTransfer` → 对 `.uppy-Dashboard-inner` 派发合成 `dragenter/dragover/drop` 事件触发 Uppy 上传 → 再点升级确认框「确定」。 -6. 下载页自包含 HTML 里若给元素自定义了 `display`(如 flex),必须补 `[hidden] { display: none !important; }`,否则 `hidden` 属性失效导致元素误显示。 - -## 测试 - -端到端验证依赖目标实例(http://192.168.3.2:8090)。公开 API 可用 curl 匿名测试: -`POST /apis/api.sharelink.halo.run/v1alpha1/downloads/-/token {"slug":"..."}` → `GET /download/{slug}/file?token=...`。 diff --git a/README.md b/README.md index bc28e7e..593d171 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ 📖 **[详细配置指南与使用说明(含截图)](docs/使用指南.md)** +## 安装 + +要求 Halo 2.22+。 + +1. 从 [Releases](https://github.com/shirainbown/plugin-sharelink/releases) 下载最新的 `sharelink-*.jar` +2. Halo Console → 左侧「插件」→ 右上角「安装」→ 上传 jar 并启用 + ## 功能 - **下载资源管理**(Console「内容 → 下载管理」):选择附件创建下载资源,生成 `/download/{slug}` 下载链接,复制后粘贴到文章即可。 diff --git a/docs/images/03-console-records.png b/docs/images/03-console-records.png index 7f6d7b0..d718c5f 100644 Binary files a/docs/images/03-console-records.png and b/docs/images/03-console-records.png differ diff --git a/docs/images/03b-console-records-email.png b/docs/images/03b-console-records-email.png index 714c45a..f94f9db 100644 Binary files a/docs/images/03b-console-records-email.png and b/docs/images/03b-console-records-email.png differ diff --git a/docs/images/12-download-page-verify-sent.png b/docs/images/12-download-page-verify-sent.png index 6303917..2daf5ba 100644 Binary files a/docs/images/12-download-page-verify-sent.png and b/docs/images/12-download-page-verify-sent.png differ diff --git a/docs/images/13-download-page-verified.png b/docs/images/13-download-page-verified.png index 4f8246c..60bd408 100644 Binary files a/docs/images/13-download-page-verified.png and b/docs/images/13-download-page-verified.png differ diff --git a/src/main/resources/console/chunks/155.a50c3903.js b/src/main/resources/console/chunks/155.a50c3903.js new file mode 100644 index 0000000..e566f38 --- /dev/null +++ b/src/main/resources/console/chunks/155.a50c3903.js @@ -0,0 +1 @@ +"use strict";(window.rspackChunksharelink=window.rspackChunksharelink||[]).push([[155],{"./node_modules/.pnpm/rspack-vue-loader@17.6.1_@rspack+core@2.1.7_@swc+helpers@0.5.23__@vue+compiler-sfc@3.5.40_vue@3.5.40_typescript@5.8.3_/node_modules/rspack-vue-loader/dist/exportHelper.js"(e,t){t.A=(e,t)=>{let a=e.__vccOpts||e;for(let[e,l]of t)a[e]=l;return a}},"./src/views/DownloadManager.vue"(e,t,a){a.r(t),a.d(t,{default:()=>en});var l=a("vue"),n=a("@halo-dev/api-client");let o="/apis/console.api.sharelink.halo.run/v1alpha1";function r(e){return{metadata:{name:e.name,creationTimestamp:e.creationTimestamp},spec:{slug:e.slug,displayName:e.displayName,description:e.description,attachmentName:e.attachmentName,requireEmailVerify:e.requireEmailVerify,enabled:e.enabled},status:{downloadCount:e.stats?.downloadCount??0},stats:{downloaderCount:e.stats?.downloaderCount??0,referenceCount:e.stats?.referenceCount??0}}}async function c(){let{data:e}=await n.axiosInstance.get(`${o}/download-resources`),t=(e??[]).map(r);return{items:t,total:t.length}}async function s(e){let{data:t}=await n.axiosInstance.post(`${o}/download-resources`,e);return r(t)}async function i(e,t){let{data:a}=await n.axiosInstance.put(`${o}/download-resources/${e}`,t);return r(a)}async function d(e){await n.axiosInstance.delete(`${o}/download-resources/${e}`)}async function u(e){let{data:t}=await n.axiosInstance.get(`${o}/download-records`,{params:e});return t}async function m(e){await n.axiosInstance.delete(`${o}/download-records/${e}`)}async function p(){let{data:e}=await n.axiosInstance.get(`${o}/references`);return e}async function f(){let{data:e}=await n.axiosInstance.post(`${o}/references/-/refresh`);return e}var v=a("@halo-dev/components");let V={class:"resource-form"},N={class:"form-item form-item--column"},y={class:"form-item form-item--column"},h=["disabled"],g={key:0,class:"form-hint"},w={key:1,class:"form-hint"},k={class:"form-item form-item--column"},E={class:"form-item form-item--column"},x={class:"attachment-picker"},C=["title"],b={key:1,class:"text-sm text-gray-400"},B={class:"form-item"},T={class:"form-item"};var _=(0,l.defineComponent)({__name:"ResourceFormModal",props:{visible:{type:Boolean},resource:{}},emits:["update:visible","saved"],setup(e,t){let{emit:a}=t,n=/^[a-z0-9][a-z0-9-]{0,63}$/,o=(0,l.computed)(()=>null!==e.resource),r=(0,l.ref)({slug:"",displayName:"",description:"",attachmentName:"",requireEmailVerify:!1,enabled:!0}),c=(0,l.ref)(null),d=(0,l.ref)(!1),u=(0,l.ref)(!1);function m(){a("update:visible",!1)}function p(e){let t=e?.[0],a=t?.metadata?.name;if(t&&a){if(c.value=t,r.value.attachmentName=a,!o.value&&!r.value.slug){let e,l=t.spec?.displayName??a;r.value.slug=(e=l.replace(/\.[^.]+$/,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64).replace(/^-+|-+$/g,""),(n.test(e)?e:"")||Math.random().toString(36).slice(2,10))}d.value=!1}}(0,l.watch)(()=>e.visible,t=>{if(t)if(c.value=null,e.resource){let t=e.resource.spec;r.value={slug:t.slug,displayName:t.displayName,description:t.description??"",attachmentName:t.attachmentName??"",requireEmailVerify:t.requireEmailVerify??!1,enabled:t.enabled??!0}}else r.value={slug:"",displayName:"",description:"",attachmentName:"",requireEmailVerify:!1,enabled:!0}});let f=(0,l.computed)(()=>{if(c.value){var e;let t=c.value.spec?.displayName??c.value.metadata?.name??"",a=(e=c.value.spec?.size)&&!(e<=0)?e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:e<0x40000000?`${(e/1024/1024).toFixed(1)} MB`:`${(e/1024/1024/1024).toFixed(2)} GB`:"";return a?`${t}(${a})`:t}return r.value.attachmentName||""});async function _(){let t=r.value.displayName.trim(),l=r.value.slug.trim();if(!t)return void v.Toast.warning("请填写资源名称");if(!l)return void v.Toast.warning("请填写 slug");if(!o.value&&!n.test(l))return void v.Toast.warning("slug 只能包含小写字母、数字和中划线,且必须以字母或数字开头(最长 64 位)");u.value=!0;try{if(o.value&&e.resource){let l={...e.resource.spec,displayName:t,description:r.value.description?.trim()??"",attachmentName:r.value.attachmentName,requireEmailVerify:r.value.requireEmailVerify,enabled:r.value.enabled},n=await i(e.resource.metadata.name,l);v.Toast.success("资源已更新"),a("saved",n,!1)}else{let e=await s({slug:l,displayName:t,description:r.value.description?.trim()??"",attachmentName:r.value.attachmentName,requireEmailVerify:r.value.requireEmailVerify,enabled:r.value.enabled});v.Toast.success("资源已创建"),a("saved",e,!0)}m()}catch(e){v.Toast.error(e?.response?.data?.detail||e?.response?.data?.title||e?.message||"保存失败,请稍后重试")}finally{u.value=!1}}return(t,n)=>{let c=(0,l.resolveComponent)("AttachmentSelectorModal");return(0,l.openBlock)(),(0,l.createElementBlock)(l.Fragment,null,[(0,l.createVNode)((0,l.unref)(v.VModal),{visible:e.visible,title:o.value?"编辑下载资源":"新建下载资源",width:640,"onUpdate:visible":n[6]||(n[6]=e=>a("update:visible",e)),onClose:m},{footer:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.VButton),{type:"primary",loading:u.value,onClick:_},{default:(0,l.withCtx)(()=>[...n[15]||(n[15]=[(0,l.createTextVNode)(" 保存 ",-1)])]),_:1},8,["loading"]),(0,l.createVNode)((0,l.unref)(v.VButton),{type:"default",onClick:m},{default:(0,l.withCtx)(()=>[...n[16]||(n[16]=[(0,l.createTextVNode)("取消",-1)])]),_:1})]),default:(0,l.withCtx)(()=>[(0,l.createElementVNode)("div",V,[(0,l.createElementVNode)("div",N,[n[8]||(n[8]=(0,l.createElementVNode)("label",{class:"form-label"},[(0,l.createTextVNode)(" 资源名称 "),(0,l.createElementVNode)("span",{class:"required"},"*")],-1)),(0,l.withDirectives)((0,l.createElementVNode)("input",{"onUpdate:modelValue":n[0]||(n[0]=e=>r.value.displayName=e),class:"form-input",type:"text",placeholder:"例如:产品白皮书 PDF"},null,512),[[l.vModelText,r.value.displayName]])]),(0,l.createElementVNode)("div",y,[n[9]||(n[9]=(0,l.createElementVNode)("label",{class:"form-label"},[(0,l.createTextVNode)(" slug "),(0,l.createElementVNode)("span",{class:"required"},"*")],-1)),(0,l.withDirectives)((0,l.createElementVNode)("input",{"onUpdate:modelValue":n[1]||(n[1]=e=>r.value.slug=e),class:"form-input",type:"text",disabled:o.value,placeholder:"例如:whitepaper-2024(下载链接为 /download/{slug})"},null,8,h),[[l.vModelText,r.value.slug]]),o.value?((0,l.openBlock)(),(0,l.createElementBlock)("div",g,"slug 创建后不可修改")):((0,l.openBlock)(),(0,l.createElementBlock)("div",w," 小写字母、数字、中划线,以字母或数字开头,最长 64 位 "))]),(0,l.createElementVNode)("div",k,[n[10]||(n[10]=(0,l.createElementVNode)("label",{class:"form-label"},"描述",-1)),(0,l.withDirectives)((0,l.createElementVNode)("textarea",{"onUpdate:modelValue":n[2]||(n[2]=e=>r.value.description=e),class:"form-input",rows:"3",placeholder:"可选,资源用途说明"},null,512),[[l.vModelText,r.value.description]])]),(0,l.createElementVNode)("div",E,[n[12]||(n[12]=(0,l.createElementVNode)("label",{class:"form-label"},"附件",-1)),(0,l.createElementVNode)("div",x,[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",onClick:n[3]||(n[3]=e=>d.value=!0)},{default:(0,l.withCtx)(()=>[...n[11]||(n[11]=[(0,l.createTextVNode)(" 选择附件 ",-1)])]),_:1}),f.value?((0,l.openBlock)(),(0,l.createElementBlock)("span",{key:0,class:"attachment-name",title:r.value.attachmentName},(0,l.toDisplayString)(f.value),9,C)):((0,l.openBlock)(),(0,l.createElementBlock)("span",b,"未选择"))])]),(0,l.createElementVNode)("div",B,[n[13]||(n[13]=(0,l.createElementVNode)("span",{class:"form-label"},"需要邮箱验证",-1)),(0,l.createVNode)((0,l.unref)(v.VSwitch),{modelValue:r.value.requireEmailVerify,"onUpdate:modelValue":n[4]||(n[4]=e=>r.value.requireEmailVerify=e)},null,8,["modelValue"])]),(0,l.createElementVNode)("div",T,[n[14]||(n[14]=(0,l.createElementVNode)("span",{class:"form-label"},"启用",-1)),(0,l.createVNode)((0,l.unref)(v.VSwitch),{modelValue:r.value.enabled,"onUpdate:modelValue":n[5]||(n[5]=e=>r.value.enabled=e)},null,8,["modelValue"])])])]),_:1},8,["visible","title"]),(0,l.createVNode)(c,{visible:d.value,"onUpdate:visible":n[7]||(n[7]=e=>d.value=e),onSelect:p},null,8,["visible"])],64)}}}),S=a("./node_modules/.pnpm/rspack-vue-loader@17.6.1_@rspack+core@2.1.7_@swc+helpers@0.5.23__@vue+compiler-sfc@3.5.40_vue@3.5.40_typescript@5.8.3_/node_modules/rspack-vue-loader/dist/exportHelper.js");let $=(0,S.A)(_,[["__scopeId","data-v-e4a7554b"]]),D={class:"records-toolbar"},I={class:"text-sm text-gray-500"},z={key:2,class:"table-wrapper"},q={class:"records-table"},U=["title"],L={key:3,class:"records-pagination"};var F=(0,l.defineComponent)({__name:"RecordsDrawer",props:{visible:{type:Boolean},resource:{}},emits:["update:visible"],setup(e,t){let{emit:a}=t,n=(0,l.ref)([]),r=(0,l.ref)(!1),c=(0,l.ref)(1),s=(0,l.ref)(20),i=(0,l.ref)(0),d=new Set;function p(e){return e?.response?.data?.detail||e?.response?.data?.title||e?.message||"请求失败,请稍后重试"}async function f(){if(e.resource){r.value=!0;try{let t=await u({resourceSlug:e.resource.spec.slug,page:c.value,size:s.value});n.value=(t.items??[]).filter(e=>!d.has(e.metadata.name)),i.value=t.total??0}catch(e){v.Toast.error(p(e))}finally{r.value=!1}}}function V(){var t;let a;window.open((a=(t=e.resource?.spec.slug)?`?resourceSlug=${encodeURIComponent(t)}`:"",`${o}/download-records/-/export${a}`),"_blank")}return(0,l.watch)(()=>[e.visible,e.resource?.metadata.name],e=>{let[t]=e;t&&(c.value=1,f())}),(t,o)=>((0,l.openBlock)(),(0,l.createBlock)((0,l.unref)(v.VModal),{visible:e.visible,title:`下载记录${e.resource?` - ${e.resource.spec.displayName}`:""}`,width:960,"mount-to-body":"","onUpdate:visible":o[2]||(o[2]=e=>a("update:visible",e)),onClose:o[3]||(o[3]=e=>a("update:visible",!1))},{default:(0,l.withCtx)(()=>[(0,l.createElementVNode)("div",D,[(0,l.createElementVNode)("div",I,"共 "+(0,l.toDisplayString)(i.value)+" 条记录",1),(0,l.createVNode)((0,l.unref)(v.VSpace),null,{default:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",onClick:V},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconRiUpload2Fill))]),default:(0,l.withCtx)(()=>[o[4]||(o[4]=(0,l.createTextVNode)(" 导出 CSV ",-1))]),_:1}),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"secondary",loading:r.value,onClick:f},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconRefreshLine))]),default:(0,l.withCtx)(()=>[o[5]||(o[5]=(0,l.createTextVNode)(" 刷新 ",-1))]),_:1},8,["loading"])]),_:1})]),r.value?((0,l.openBlock)(),(0,l.createBlock)((0,l.unref)(v.VLoading),{key:0})):0===n.value.length?((0,l.openBlock)(),(0,l.createBlock)((0,l.unref)(v.VEmpty),{key:1,title:"暂无下载记录",message:"该资源还没有产生下载"})):((0,l.openBlock)(),(0,l.createElementBlock)("div",z,[(0,l.createElementVNode)("table",q,[o[7]||(o[7]=(0,l.createElementVNode)("thead",null,[(0,l.createElementVNode)("tr",null,[(0,l.createElementVNode)("th",null,"时间"),(0,l.createElementVNode)("th",null,"邮箱"),(0,l.createElementVNode)("th",null,"IP"),(0,l.createElementVNode)("th",null,"User-Agent"),(0,l.createElementVNode)("th",{class:"w-24"},"操作")])],-1)),(0,l.createElementVNode)("tbody",null,[((0,l.openBlock)(!0),(0,l.createElementBlock)(l.Fragment,null,(0,l.renderList)(n.value,e=>((0,l.openBlock)(),(0,l.createElementBlock)("tr",{key:e.metadata.name},[(0,l.createElementVNode)("td",null,(0,l.toDisplayString)(function(e){if(!e)return"-";let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString("zh-CN",{hour12:!1})}(e.spec.downloadedAt)),1),(0,l.createElementVNode)("td",null,(0,l.toDisplayString)(e.spec.email||"匿名"),1),(0,l.createElementVNode)("td",null,(0,l.toDisplayString)(e.spec.ip||"-"),1),(0,l.createElementVNode)("td",{class:"ua-cell",title:e.spec.userAgent},(0,l.toDisplayString)(e.spec.userAgent||"-"),9,U),(0,l.createElementVNode)("td",null,[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"danger",ghost:"",onClick:t=>{v.Dialog.warning({title:"删除下载记录",description:"确定要删除这条下载记录吗?删除后不可恢复。",confirmType:"danger",confirmText:"删除",cancelText:"取消",onConfirm:async()=>{try{await m(e.metadata.name),1===n.value.length&&c.value>1&&(c.value-=1),d.add(e.metadata.name),setTimeout(()=>d.delete(e.metadata.name),3e3),n.value=n.value.filter(t=>t.metadata.name!==e.metadata.name),v.Toast.success("记录已删除"),f()}catch(e){v.Toast.error(p(e))}}})}},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconDeleteBin))]),default:(0,l.withCtx)(()=>[o[6]||(o[6]=(0,l.createTextVNode)(" 删除 ",-1))]),_:1},8,["onClick"])])]))),128))])])])),i.value>0?((0,l.openBlock)(),(0,l.createElementBlock)("div",L,[(0,l.createVNode)((0,l.unref)(v.VPagination),{page:c.value,"onUpdate:page":o[0]||(o[0]=e=>c.value=e),size:s.value,"onUpdate:size":o[1]||(o[1]=e=>s.value=e),total:i.value,"size-options":[20,50,100],"show-total":"",onChange:f},null,8,["page","size","total"])])):(0,l.createCommentVNode)("",!0)]),_:1},8,["visible","title"]))}});let M=(0,S.A)(F,[["__scopeId","data-v-0f8276c1"]]),A={class:"download-manager-page m-4"},R={class:"card-toolbar"},P={class:"text-sm text-gray-500"},H={key:2,class:"table-wrapper"},j={class:"resource-table"},G={class:"font-medium"},K=["title"],O={class:"link-cell"},J={class:"link-code"},Q=["title"],W={key:0,class:"refs-row"},X={colspan:"9"},Y={key:0,class:"text-sm text-gray-400 refs-empty"},Z={key:1,class:"refs-list"},ee={class:"refs-title"},et=["href"],ea=["href"];var el=(0,l.defineComponent)({__name:"DownloadManager",setup(e){let t=(0,l.ref)([]),a=(0,l.ref)(!1),n=(0,l.ref)({}),o=(0,l.ref)(!1),r=(0,l.ref)(!1),s=new Set,u=(0,l.ref)(new Set),m=(0,l.ref)(!1),V=(0,l.ref)(null),N=(0,l.ref)(!1),y=(0,l.ref)(null);function h(e){return e?.response?.data?.detail||e?.response?.data?.title||e?.message||"请求失败,请稍后重试"}async function g(e){let t=`${window.location.origin}/download/${e}`;try{await navigator.clipboard.writeText(t),v.Toast.success("下载链接已复制")}catch{let e=document.createElement("textarea");e.value=t,document.body.appendChild(e),e.select();try{document.execCommand("copy"),v.Toast.success("下载链接已复制")}catch{v.Toast.error("复制失败,请手动复制")}document.body.removeChild(e)}}async function w(){a.value=!0;try{t.value=((await c()).items??[]).filter(e=>!s.has(e.metadata.name))}catch(e){v.Toast.error(h(e))}finally{a.value=!1}}async function k(){o.value=!0;try{n.value=await p()??{}}catch(e){v.Toast.error(h(e))}finally{o.value=!1}}async function E(){r.value=!0;try{n.value=await f()??{},v.Toast.success("引用扫描已刷新"),w()}catch(e){v.Toast.error(h(e))}finally{r.value=!1}}function x(){V.value=null,m.value=!0}async function C(e,t){let a=e.spec.enabled;e.spec.enabled=t;try{await i(e.metadata.name,{...e.spec,enabled:t}),v.Toast.success(t?"已启用":"已停用")}catch(t){e.spec.enabled=a,v.Toast.error(h(t)),w()}}function b(e,t){w(),t&&e.spec.enabled&&v.Toast.success(`下载链接:/download/${e.spec.slug}`)}return(0,l.onMounted)(()=>{w(),k()}),(e,c)=>((0,l.openBlock)(),(0,l.createElementBlock)(l.Fragment,null,[(0,l.createVNode)((0,l.unref)(v.VPageHeader),{title:"下载管理"}),(0,l.createElementVNode)("div",A,[(0,l.createVNode)((0,l.unref)(v.VCard),null,{default:(0,l.withCtx)(()=>[(0,l.createElementVNode)("div",R,[(0,l.createElementVNode)("div",P," 共 "+(0,l.toDisplayString)(t.value.length)+" 个下载资源 ",1),(0,l.createVNode)((0,l.unref)(v.VSpace),null,{default:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"primary",onClick:x},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconAddCircle))]),default:(0,l.withCtx)(()=>[c[3]||(c[3]=(0,l.createTextVNode)(" 新建资源 ",-1))]),_:1}),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"secondary",loading:r.value,onClick:E},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconRefreshLine))]),default:(0,l.withCtx)(()=>[c[4]||(c[4]=(0,l.createTextVNode)(" 刷新引用扫描 ",-1))]),_:1},8,["loading"]),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",loading:a.value||o.value,onClick:c[0]||(c[0]=e=>{w(),k()})},{default:(0,l.withCtx)(()=>[...c[5]||(c[5]=[(0,l.createTextVNode)(" 刷新 ",-1)])]),_:1},8,["loading"])]),_:1})]),a.value?((0,l.openBlock)(),(0,l.createBlock)((0,l.unref)(v.VLoading),{key:0})):0===t.value.length?((0,l.openBlock)(),(0,l.createBlock)((0,l.unref)(v.VEmpty),{key:1,title:"暂无下载资源",message:"点击右上角「新建资源」创建第一个资源下载链接"})):((0,l.openBlock)(),(0,l.createElementBlock)("div",H,[(0,l.createElementVNode)("table",j,[c[12]||(c[12]=(0,l.createElementVNode)("thead",null,[(0,l.createElementVNode)("tr",null,[(0,l.createElementVNode)("th",null,"名称"),(0,l.createElementVNode)("th",null,"下载链接"),(0,l.createElementVNode)("th",null,"附件"),(0,l.createElementVNode)("th",null,"邮箱验证"),(0,l.createElementVNode)("th",null,"启用"),(0,l.createElementVNode)("th",null,"引用文章"),(0,l.createElementVNode)("th",null,"下载数"),(0,l.createElementVNode)("th",null,"下载人数"),(0,l.createElementVNode)("th",{class:"w-56"},"操作")])],-1)),(0,l.createElementVNode)("tbody",null,[((0,l.openBlock)(!0),(0,l.createElementBlock)(l.Fragment,null,(0,l.renderList)(t.value,e=>((0,l.openBlock)(),(0,l.createElementBlock)(l.Fragment,{key:e.metadata.name},[(0,l.createElementVNode)("tr",null,[(0,l.createElementVNode)("td",G,[(0,l.createElementVNode)("div",null,(0,l.toDisplayString)(e.spec.displayName),1),e.spec.description?((0,l.openBlock)(),(0,l.createElementBlock)("div",{key:0,class:"text-xs text-gray-400 desc-line",title:e.spec.description},(0,l.toDisplayString)(e.spec.description),9,K)):(0,l.createCommentVNode)("",!0)]),(0,l.createElementVNode)("td",null,[(0,l.createElementVNode)("div",O,[(0,l.createElementVNode)("code",J,"/download/"+(0,l.toDisplayString)(e.spec.slug),1),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",ghost:"",onClick:t=>g(e.spec.slug)},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconClipboardLine))]),default:(0,l.withCtx)(()=>[c[6]||(c[6]=(0,l.createTextVNode)(" 复制 ",-1))]),_:1},8,["onClick"])])]),(0,l.createElementVNode)("td",{class:"cell-ellipsis",title:e.spec.attachmentName},(0,l.toDisplayString)(e.spec.attachmentName||"-"),9,Q),(0,l.createElementVNode)("td",null,[(0,l.createVNode)((0,l.unref)(v.VTag),{theme:e.spec.requireEmailVerify?"primary":"default"},{default:(0,l.withCtx)(()=>[(0,l.createTextVNode)((0,l.toDisplayString)(e.spec.requireEmailVerify?"需要":"不需要"),1)]),_:2},1032,["theme"])]),(0,l.createElementVNode)("td",null,[(0,l.createVNode)((0,l.unref)(v.VSwitch),{"model-value":e.spec.enabled??!1,onChange:t=>C(e,t)},null,8,["model-value","onChange"])]),(0,l.createElementVNode)("td",null,[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",ghost:"",onClick:t=>{var a;let l;return a=e.spec.slug,void((l=new Set(u.value)).has(a)?l.delete(a):l.add(a),u.value=l)}},{default:(0,l.withCtx)(()=>[(0,l.createTextVNode)((0,l.toDisplayString)(e.stats?.referenceCount??0)+" 篇 "+(0,l.toDisplayString)(u.value.has(e.spec.slug)?"▲":"▼"),1)]),_:2},1032,["onClick"])]),(0,l.createElementVNode)("td",null,(0,l.toDisplayString)(e.status?.downloadCount??0),1),(0,l.createElementVNode)("td",null,(0,l.toDisplayString)(e.stats?.downloaderCount??0),1),(0,l.createElementVNode)("td",null,[(0,l.createVNode)((0,l.unref)(v.VSpace),null,{default:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"default",onClick:t=>{y.value=e,N.value=!0}},{default:(0,l.withCtx)(()=>[...c[7]||(c[7]=[(0,l.createTextVNode)(" 记录 ",-1)])]),_:1},8,["onClick"]),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"secondary",onClick:t=>{V.value=e,m.value=!0}},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconRiPencilFill))]),default:(0,l.withCtx)(()=>[c[8]||(c[8]=(0,l.createTextVNode)(" 编辑 ",-1))]),_:1},8,["onClick"]),(0,l.createVNode)((0,l.unref)(v.VButton),{size:"sm",type:"danger",ghost:"",onClick:a=>{v.Dialog.warning({title:"删除下载资源",description:`确定要删除资源「${e.spec.displayName}」吗?删除后下载链接 /download/${e.spec.slug} 将立即失效。`,confirmType:"danger",confirmText:"删除",cancelText:"取消",onConfirm:async()=>{try{await d(e.metadata.name),s.add(e.metadata.name),setTimeout(()=>s.delete(e.metadata.name),3e3),t.value=t.value.filter(t=>t.metadata.name!==e.metadata.name),v.Toast.success(`已删除资源「${e.spec.displayName}」`),w()}catch(e){v.Toast.error(h(e))}}})}},{icon:(0,l.withCtx)(()=>[(0,l.createVNode)((0,l.unref)(v.IconDeleteBin))]),default:(0,l.withCtx)(()=>[c[9]||(c[9]=(0,l.createTextVNode)(" 删除 ",-1))]),_:1},8,["onClick"])]),_:2},1024)])]),u.value.has(e.spec.slug)?((0,l.openBlock)(),(0,l.createElementBlock)("tr",W,[(0,l.createElementVNode)("td",X,[0===(n.value[e.spec.slug]??[]).length?((0,l.openBlock)(),(0,l.createElementBlock)("div",Y," 暂无文章引用该资源(可在编辑器中插入 /download/"+(0,l.toDisplayString)(e.spec.slug)+" 链接后点击「刷新引用扫描」) ",1)):((0,l.openBlock)(),(0,l.createElementBlock)("ul",Z,[((0,l.openBlock)(!0),(0,l.createElementBlock)(l.Fragment,null,(0,l.renderList)(n.value[e.spec.slug],e=>((0,l.openBlock)(),(0,l.createElementBlock)("li",{key:e.postName},[(0,l.createElementVNode)("span",ee,(0,l.toDisplayString)(e.title),1),(0,l.createElementVNode)("a",{class:"refs-link",href:e.editorUrl,target:"_blank",rel:"noopener noreferrer"},[(0,l.createVNode)((0,l.unref)(v.IconExternalLinkLine)),c[10]||(c[10]=(0,l.createTextVNode)(" 编辑器 ",-1))],8,et),(0,l.createElementVNode)("a",{class:"refs-link",href:e.permalink,target:"_blank",rel:"noopener noreferrer"},[(0,l.createVNode)((0,l.unref)(v.IconExternalLinkLine)),c[11]||(c[11]=(0,l.createTextVNode)(" 访问 ",-1))],8,ea)]))),128))]))])])):(0,l.createCommentVNode)("",!0)],64))),128))])])]))]),_:1}),(0,l.createVNode)($,{visible:m.value,"onUpdate:visible":c[1]||(c[1]=e=>m.value=e),resource:V.value,onSaved:b},null,8,["visible","resource"]),(0,l.createVNode)(M,{visible:N.value,"onUpdate:visible":c[2]||(c[2]=e=>N.value=e),resource:y.value},null,8,["visible","resource"])])],64))}}),en=(0,S.A)(el,[["__scopeId","data-v-323caff7"]])}}]); \ No newline at end of file diff --git a/src/main/resources/console/chunks/155.c8ed3587.css b/src/main/resources/console/chunks/155.c8ed3587.css new file mode 100644 index 0000000..c142650 --- /dev/null +++ b/src/main/resources/console/chunks/155.c8ed3587.css @@ -0,0 +1 @@ +.resource-form[data-v-e4a7554b]{flex-direction:column;gap:14px;display:flex}.form-item[data-v-e4a7554b]{justify-content:space-between;align-items:center;gap:12px;display:flex}.form-item--column[data-v-e4a7554b]{flex-direction:column;align-items:stretch;gap:6px}.form-label[data-v-e4a7554b]{color:#374151;font-size:14px}.required[data-v-e4a7554b]{color:#dc2626}.form-input[data-v-e4a7554b]{box-sizing:border-box;border:1px solid #d1d5db;border-radius:4px;outline:none;width:100%;padding:6px 10px;font-size:14px}.form-input[data-v-e4a7554b]:focus{border-color:#2563eb}.form-input[data-v-e4a7554b]:disabled{color:#6b7280;cursor:not-allowed;background:#f3f4f6}.form-hint[data-v-e4a7554b]{color:#9ca3af;font-size:12px}.attachment-picker[data-v-e4a7554b]{align-items:center;gap:10px;display:flex}.attachment-name[data-v-e4a7554b]{color:#374151;text-overflow:ellipsis;white-space:nowrap;max-width:380px;font-size:13px;overflow:hidden}.records-toolbar[data-v-0f8276c1]{justify-content:space-between;align-items:center;margin-bottom:12px;display:flex}.table-wrapper[data-v-0f8276c1]{overflow-x:auto}.records-table[data-v-0f8276c1]{border-collapse:collapse;width:100%;font-size:14px}.records-table th[data-v-0f8276c1],.records-table td[data-v-0f8276c1]{text-align:left;white-space:nowrap;border-bottom:1px solid #f0f0f0;padding:8px 12px}.records-table th[data-v-0f8276c1]{color:#6b7280;background:#fafafa;font-weight:500}.ua-cell[data-v-0f8276c1]{text-overflow:ellipsis;max-width:260px;overflow:hidden}.records-pagination[data-v-0f8276c1]{justify-content:flex-end;margin-top:12px;display:flex}.card-toolbar[data-v-323caff7]{justify-content:space-between;align-items:center;margin-bottom:12px;display:flex}.table-wrapper[data-v-323caff7]{overflow-x:auto}.resource-table[data-v-323caff7]{border-collapse:collapse;width:100%;font-size:14px}.resource-table th[data-v-323caff7],.resource-table td[data-v-323caff7]{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #f0f0f0;padding:10px 12px}.resource-table th[data-v-323caff7]{color:#6b7280;background:#fafafa;font-weight:500}.link-cell[data-v-323caff7]{align-items:center;gap:8px;display:flex}.link-code[data-v-323caff7]{background:#f3f4f6;border-radius:4px;padding:2px 6px;font-size:12px}.cell-ellipsis[data-v-323caff7]{text-overflow:ellipsis;max-width:180px;overflow:hidden}.desc-line[data-v-323caff7]{text-overflow:ellipsis;white-space:nowrap;max-width:200px;overflow:hidden}.refs-row td[data-v-323caff7]{white-space:normal;background:#fafafa}.refs-empty[data-v-323caff7]{padding:4px 0}.refs-list[data-v-323caff7]{margin:0;padding:0;list-style:none}.refs-list li[data-v-323caff7]{align-items:center;gap:12px;padding:4px 0;display:flex}.refs-title[data-v-323caff7]{font-weight:500}.refs-link[data-v-323caff7]{color:#2563eb;align-items:center;gap:2px;font-size:13px;text-decoration:none;display:inline-flex}.refs-link[data-v-323caff7]:hover{text-decoration:underline} \ No newline at end of file diff --git a/src/main/resources/console/main.js b/src/main/resources/console/main.js new file mode 100644 index 0000000..7df4b0b --- /dev/null +++ b/src/main/resources/console/main.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,n,t,r,o,i={"./src/index.ts"(e,n,t){t.d(n,{default:()=>a});var r=t("@halo-dev/components"),o=window.HaloUiShared,i=t("vue"),a=(0,o.definePlugin)({components:{},routes:[{parentName:"Root",route:{path:"/download-manager",name:"DownloadManager",component:()=>t.e(155).then(t.bind(t,"./src/views/DownloadManager.vue")),meta:{title:"下载管理",permissions:["plugin:sharelink:view"],menu:{name:"下载管理",group:"content",icon:(0,i.markRaw)(r.IconArrowDownCircleLine),priority:52}}}}],extensionPoints:{}})},"@halo-dev/api-client"(e){e.exports=window.HaloApiClient},"@halo-dev/components"(e){e.exports=window.HaloComponents},vue(e){e.exports=window.Vue}},a={};function s(e){var n=a[e];if(void 0!==n)return n.exports;var t=a[e]={exports:{}};return i[e](t,t.exports,s),t.exports}if(s.m=i,s.d=(e,n,t)=>{var r=(n,t)=>{for(var r in n)s.o(n,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,[t]:n[r]})};r(n,"get"),r(t,"value")},s.f={},s.e=e=>Promise.all(Object.keys(s.f).reduce((n,t)=>(s.f[t](e,n),n),[])),s.u=e=>155===e?"chunks/155.a50c3903.js":""+e+".javascript",s.miniCssF=e=>155===e?"chunks/155.c8ed3587.css":""+e+".css",s.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),e={},n="sharelink:",s.l=function(t,r,o,i){if(e[t])return void e[t].push(r);if(void 0!==o)for(var a,l,u=document.getElementsByTagName("script"),d=0;d{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.p="/plugins/sharelink/assets/console/","u">typeof document){var l=function(e,n,t,r,o,i){var a=document.createElement("link");return a.rel="stylesheet",a.type="text/css",s.nc&&(a.nonce=s.nc),a.href=n,a.onerror=a.onload=function(t){if(a.onerror=a.onload=null,"load"===t.type)r();else{var i=t&&("load"===t.type?"missing":t.type),s=t&&t.target&&t.target.href||n,l=Error("Loading CSS chunk "+e+" failed.\\n("+s+")");l.code="CSS_CHUNK_LOAD_FAILED",l.type=i,l.request=s,a.parentNode&&a.parentNode.removeChild(a),o(l)}},t?t.parentNode.insertBefore(a,t.nextSibling):document.head.appendChild(a),a},u=function(e,n){for(var t=document.getElementsByTagName("link"),r=0;rr=t[e]=[n,o]);n.push(r[2]=o);var i=s.p+s.u(e),a=Error();s.l(i,function(n){if(s.o(t,e)&&(0!==(r=t[e])&&(t[e]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),i=n&&n.target&&n.target.src;a.message="Loading chunk "+e+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,r[1](a)}},"chunk-"+e,e)}},r=(e,n)=>{var r,o,[i,a,l]=n,u=0;if(i.some(e=>0!==t[e])){for(r in a)s.o(a,r)&&(s.m[r]=a[r]);l&&l(s)}for(e&&e(n);u=2.22.0" author: - name: ssy - website: https://git.ali.songshiyu.cn/ssy + name: shirainbown + website: https://github.com/shirainbown logo: logo.svg - homepage: https://git.ali.songshiyu.cn/ssy/sharelink - repo: https://git.ali.songshiyu.cn/ssy/sharelink - issues: https://git.ali.songshiyu.cn/ssy/sharelink/issues + homepage: https://github.com/shirainbown/plugin-sharelink + repo: https://github.com/shirainbown/plugin-sharelink + issues: https://github.com/shirainbown/plugin-sharelink/issues displayName: "资源下载管理" description: "管理文章中的资源下载链接:下载统计、按资源的邮箱验证、防止附件直链下载、文章引用扫描。" + version: "@version@" configMapName: sharelink-configmap settingName: sharelink-settings license: