hakumi_components 0.1.18.pre → 0.1.19.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -2
- data/README.md +208 -381
- data/app/assets/javascripts/hakumi_components.js +40 -34
- data/app/assets/stylesheets/hakumi_components.css +1 -1
- data/app/components/hakumi_components/admin_panel/component.rb +19 -9
- data/app/components/hakumi_components/affix/component.rb +54 -30
- data/app/components/hakumi_components/alert/component.html.erb +2 -2
- data/app/components/hakumi_components/alert/component.rb +68 -18
- data/app/components/hakumi_components/anchor/component.rb +35 -27
- data/app/components/hakumi_components/anchor/link/component.html.erb +1 -1
- data/app/components/hakumi_components/anchor/link/component.rb +28 -9
- data/app/components/hakumi_components/autocomplete/component.html.erb +86 -66
- data/app/components/hakumi_components/autocomplete/component.rb +115 -89
- data/app/components/hakumi_components/avatar/component.rb +45 -12
- data/app/components/hakumi_components/badge/component.rb +99 -45
- data/app/components/hakumi_components/base_component.rb +290 -91
- data/app/components/hakumi_components/breadcrumb/component.rb +16 -5
- data/app/components/hakumi_components/breadcrumb/item/component.html.erb +5 -5
- data/app/components/hakumi_components/breadcrumb/item/component.rb +56 -8
- data/app/components/hakumi_components/breadcrumb/item/menu_entry.rb +71 -0
- data/app/components/hakumi_components/button/component.rb +62 -20
- data/app/components/hakumi_components/calendar/component.html.erb +19 -19
- data/app/components/hakumi_components/calendar/component.rb +225 -202
- data/app/components/hakumi_components/calendar/day_cell.rb +62 -0
- data/app/components/hakumi_components/calendar/display_model.rb +237 -0
- data/app/components/hakumi_components/calendar/event_entry.rb +54 -0
- data/app/components/hakumi_components/calendar/locale_pack.rb +28 -0
- data/app/components/hakumi_components/calendar/panel_entry.rb +48 -0
- data/app/components/hakumi_components/calendar/week_row.rb +28 -0
- data/app/components/hakumi_components/card/component.html.erb +2 -2
- data/app/components/hakumi_components/card/component.rb +76 -49
- data/app/components/hakumi_components/card/grid/component.html.erb +1 -1
- data/app/components/hakumi_components/card/grid/component.rb +23 -17
- data/app/components/hakumi_components/card/meta/component.html.erb +8 -8
- data/app/components/hakumi_components/card/meta/component.rb +42 -24
- data/app/components/hakumi_components/carousel/autoplay_config.rb +27 -0
- data/app/components/hakumi_components/carousel/component.rb +85 -67
- data/app/components/hakumi_components/carousel/dots_config.rb +36 -0
- data/app/components/hakumi_components/cascader/component.html.erb +54 -36
- data/app/components/hakumi_components/cascader/component.rb +111 -62
- data/app/components/hakumi_components/checkbox/component.html.erb +5 -5
- data/app/components/hakumi_components/checkbox/component.rb +40 -6
- data/app/components/hakumi_components/checkbox/group/component.html.erb +4 -4
- data/app/components/hakumi_components/checkbox/group/component.rb +40 -13
- data/app/components/hakumi_components/checkbox/group/option.rb +34 -0
- data/app/components/hakumi_components/collapse/component.rb +64 -48
- data/app/components/hakumi_components/collapse/panel/component.rb +64 -21
- data/app/components/hakumi_components/color_picker/component.html.erb +21 -22
- data/app/components/hakumi_components/color_picker/component.rb +159 -49
- data/app/components/hakumi_components/color_picker/preset_group.rb +27 -0
- data/app/components/hakumi_components/concerns/form_field.rb +169 -68
- data/app/components/hakumi_components/concerns/form_field_contract.rb +57 -0
- data/app/components/hakumi_components/concerns/form_field_interface.rb +16 -0
- data/app/components/hakumi_components/concerns/input_control.rb +111 -0
- data/app/components/hakumi_components/concerns/input_control_contract.rb +47 -0
- data/app/components/hakumi_components/concerns/input_control_interface.rb +34 -0
- data/app/components/hakumi_components/concerns/selection_control.rb +122 -0
- data/app/components/hakumi_components/concerns/selection_control_contract.rb +47 -0
- data/app/components/hakumi_components/concerns/selection_control_interface.rb +34 -0
- data/app/components/hakumi_components/container/component.rb +29 -14
- data/app/components/hakumi_components/date_picker/component.html.erb +1 -3
- data/app/components/hakumi_components/date_picker/component.rb +130 -16
- data/app/components/hakumi_components/date_picker/range_picker.rb +159 -28
- data/app/components/hakumi_components/date_picker/shared_rendering.rb +151 -61
- data/app/components/hakumi_components/descriptions/component.html.erb +6 -6
- data/app/components/hakumi_components/descriptions/component.rb +98 -61
- data/app/components/hakumi_components/descriptions/item/component.rb +32 -8
- data/app/components/hakumi_components/divider/component.rb +36 -16
- data/app/components/hakumi_components/drawer/component.html.erb +2 -2
- data/app/components/hakumi_components/drawer/component.rb +112 -29
- data/app/components/hakumi_components/dropdown/component.rb +32 -35
- data/app/components/hakumi_components/dropdown/divider/component.rb +4 -0
- data/app/components/hakumi_components/dropdown/item/component.rb +42 -16
- data/app/components/hakumi_components/empty/component.rb +66 -31
- data/app/components/hakumi_components/flex/component.rb +39 -21
- data/app/components/hakumi_components/float_button/back_top/component.rb +35 -28
- data/app/components/hakumi_components/float_button/component.rb +161 -75
- data/app/components/hakumi_components/float_button/group/component.rb +85 -98
- data/app/components/hakumi_components/float_button/group_cluster/component.rb +46 -54
- data/app/components/hakumi_components/float_button/group_spec.rb +259 -0
- data/app/components/hakumi_components/float_button/item_spec.rb +115 -0
- data/app/components/hakumi_components/float_button/position_config.rb +68 -0
- data/app/components/hakumi_components/form/item/component.rb +34 -27
- data/app/components/hakumi_components/grid/col/component.rb +88 -17
- data/app/components/hakumi_components/grid/row/component.rb +35 -17
- data/app/components/hakumi_components/icon/component.rb +49 -49
- data/app/components/hakumi_components/image/component.html.erb +7 -7
- data/app/components/hakumi_components/image/component.rb +103 -40
- data/app/components/hakumi_components/image/preview_group/component.html.erb +1 -1
- data/app/components/hakumi_components/image/preview_group/component.rb +41 -36
- data/app/components/hakumi_components/image/preview_group/item.rb +48 -0
- data/app/components/hakumi_components/input/component.html.erb +1 -1
- data/app/components/hakumi_components/input/component.rb +142 -66
- data/app/components/hakumi_components/input/password/component.rb +30 -41
- data/app/components/hakumi_components/input/text_area/component.html.erb +2 -2
- data/app/components/hakumi_components/input/text_area/component.rb +89 -42
- data/app/components/hakumi_components/input_number/component.html.erb +5 -5
- data/app/components/hakumi_components/input_number/component.rb +137 -55
- data/app/components/hakumi_components/layout/component.html.erb +1 -1
- data/app/components/hakumi_components/layout/component.rb +23 -16
- data/app/components/hakumi_components/layout/content/component.html.erb +1 -1
- data/app/components/hakumi_components/layout/content/component.rb +24 -14
- data/app/components/hakumi_components/layout/footer/component.html.erb +1 -1
- data/app/components/hakumi_components/layout/footer/component.rb +24 -14
- data/app/components/hakumi_components/layout/header/component.html.erb +1 -1
- data/app/components/hakumi_components/layout/header/component.rb +24 -14
- data/app/components/hakumi_components/layout/sider/component.html.erb +1 -1
- data/app/components/hakumi_components/layout/sider/component.rb +83 -49
- data/app/components/hakumi_components/mentions/coercion.rb +46 -0
- data/app/components/hakumi_components/mentions/component.html.erb +6 -6
- data/app/components/hakumi_components/mentions/component.rb +131 -67
- data/app/components/hakumi_components/mentions/option.rb +30 -0
- data/app/components/hakumi_components/menu/component.rb +50 -59
- data/app/components/hakumi_components/menu/divider/component.rb +4 -0
- data/app/components/hakumi_components/menu/group/component.rb +16 -15
- data/app/components/hakumi_components/menu/item/component.rb +42 -19
- data/app/components/hakumi_components/menu/sub_menu/component.rb +49 -37
- data/app/components/hakumi_components/message/component.rb +62 -27
- data/app/components/hakumi_components/modal/component.html.erb +6 -6
- data/app/components/hakumi_components/modal/component.rb +93 -40
- data/app/components/hakumi_components/modal/confirm/component.html.erb +2 -2
- data/app/components/hakumi_components/modal/confirm/component.rb +49 -8
- data/app/components/hakumi_components/modal/error/component.rb +8 -39
- data/app/components/hakumi_components/modal/info/component.rb +6 -40
- data/app/components/hakumi_components/modal/status_component.rb +150 -0
- data/app/components/hakumi_components/modal/success/component.rb +6 -40
- data/app/components/hakumi_components/modal/warning/component.rb +14 -44
- data/app/components/hakumi_components/notification/component.rb +72 -38
- data/app/components/hakumi_components/pagination/component.html.erb +7 -11
- data/app/components/hakumi_components/pagination/component.rb +105 -72
- data/app/components/hakumi_components/pagination/page_item.rb +49 -0
- data/app/components/hakumi_components/popconfirm/component.rb +67 -19
- data/app/components/hakumi_components/popover/component.html.erb +2 -2
- data/app/components/hakumi_components/popover/component.rb +102 -38
- data/app/components/hakumi_components/progress/attribute_renderer.rb +354 -0
- data/app/components/hakumi_components/progress/circle_geometry.rb +76 -0
- data/app/components/hakumi_components/progress/component.html.erb +18 -17
- data/app/components/hakumi_components/progress/component.rb +230 -402
- data/app/components/hakumi_components/progress/controller_locals_parser.rb +130 -0
- data/app/components/hakumi_components/progress/info_tooltip_policy.rb +39 -0
- data/app/components/hakumi_components/progress/info_value.rb +75 -0
- data/app/components/hakumi_components/progress/status_state.rb +31 -0
- data/app/components/hakumi_components/progress/steps_renderer.rb +149 -0
- data/app/components/hakumi_components/progress/stroke_gradient_value.rb +90 -0
- data/app/components/hakumi_components/qr_code/component.rb +138 -72
- data/app/components/hakumi_components/radio/component.html.erb +3 -3
- data/app/components/hakumi_components/radio/component.rb +42 -9
- data/app/components/hakumi_components/radio/group/component.html.erb +9 -9
- data/app/components/hakumi_components/radio/group/component.rb +76 -27
- data/app/components/hakumi_components/radio/group/option.rb +43 -0
- data/app/components/hakumi_components/rate/component.html.erb +1 -1
- data/app/components/hakumi_components/rate/component.rb +55 -9
- data/app/components/hakumi_components/result/component.rb +87 -37
- data/app/components/hakumi_components/segmented/component.html.erb +1 -1
- data/app/components/hakumi_components/segmented/component.rb +81 -118
- data/app/components/hakumi_components/segmented/option.rb +191 -0
- data/app/components/hakumi_components/select/component.html.erb +19 -19
- data/app/components/hakumi_components/select/component.rb +88 -37
- data/app/components/hakumi_components/selection_control/coercion.rb +161 -0
- data/app/components/hakumi_components/selection_control/entry.rb +22 -0
- data/app/components/hakumi_components/selection_control/option.rb +42 -0
- data/app/components/hakumi_components/selection_control/option_group.rb +34 -0
- data/app/components/hakumi_components/selection_control/tree_node.rb +123 -0
- data/app/components/hakumi_components/skeleton/avatar/component.rb +36 -14
- data/app/components/hakumi_components/skeleton/avatar_config.rb +79 -0
- data/app/components/hakumi_components/skeleton/button/component.rb +32 -14
- data/app/components/hakumi_components/skeleton/component.rb +79 -93
- data/app/components/hakumi_components/skeleton/image/component.rb +46 -22
- data/app/components/hakumi_components/skeleton/input/component.rb +25 -10
- data/app/components/hakumi_components/skeleton/node/component.rb +23 -7
- data/app/components/hakumi_components/skeleton/paragraph_config.rb +92 -0
- data/app/components/hakumi_components/skeleton/title_config.rb +31 -0
- data/app/components/hakumi_components/slider/component.html.erb +11 -11
- data/app/components/hakumi_components/slider/component.rb +172 -53
- data/app/components/hakumi_components/slider/mark.rb +27 -0
- data/app/components/hakumi_components/space/compact/component.html.erb +1 -3
- data/app/components/hakumi_components/space/compact/component.rb +35 -19
- data/app/components/hakumi_components/space/component.html.erb +1 -1
- data/app/components/hakumi_components/space/component.rb +75 -39
- data/app/components/hakumi_components/spin/component.rb +92 -31
- data/app/components/hakumi_components/splitter/component.html.erb +9 -3
- data/app/components/hakumi_components/splitter/component.rb +55 -22
- data/app/components/hakumi_components/splitter/panel/component.html.erb +1 -3
- data/app/components/hakumi_components/splitter/panel/component.rb +64 -39
- data/app/components/hakumi_components/statistic/component.rb +143 -59
- data/app/components/hakumi_components/steps/component.rb +87 -55
- data/app/components/hakumi_components/steps/item/component.rb +59 -20
- data/app/components/hakumi_components/switch/component.html.erb +2 -3
- data/app/components/hakumi_components/switch/component.rb +62 -36
- data/app/components/hakumi_components/table/column/component.rb +80 -23
- data/app/components/hakumi_components/table/column_definition.rb +181 -0
- data/app/components/hakumi_components/table/column_group/component.rb +32 -21
- data/app/components/hakumi_components/table/component.html.erb +22 -31
- data/app/components/hakumi_components/table/component.rb +248 -509
- data/app/components/hakumi_components/table/concerns/attribute_helpers.rb +22 -0
- data/app/components/hakumi_components/table/concerns/cell_rendering.rb +277 -0
- data/app/components/hakumi_components/table/concerns/columns.rb +293 -91
- data/app/components/hakumi_components/table/concerns/editable.rb +36 -89
- data/app/components/hakumi_components/table/concerns/ellipsis.rb +31 -30
- data/app/components/hakumi_components/table/concerns/fixed_columns.rb +26 -19
- data/app/components/hakumi_components/table/concerns/surface_rendering.rb +365 -0
- data/app/components/hakumi_components/table/configs.rb +518 -0
- data/app/components/hakumi_components/table/definition_types.rb +40 -0
- data/app/components/hakumi_components/table/display_rows.rb +56 -0
- data/app/components/hakumi_components/table/ellipsis_config.rb +143 -0
- data/app/components/hakumi_components/table/fixed_offset.rb +36 -0
- data/app/components/hakumi_components/table/header_cell.rb +23 -0
- data/app/components/hakumi_components/table/row_record.rb +60 -0
- data/app/components/hakumi_components/table/row_render_state.rb +85 -0
- data/app/components/hakumi_components/tabs/component.html.erb +8 -8
- data/app/components/hakumi_components/tabs/component.rb +114 -64
- data/app/components/hakumi_components/tabs/indicator_config.rb +34 -0
- data/app/components/hakumi_components/tabs/item/component.rb +73 -13
- data/app/components/hakumi_components/tag/component.rb +142 -47
- data/app/components/hakumi_components/tag/group/component.rb +18 -6
- data/app/components/hakumi_components/time_picker/component.html.erb +8 -8
- data/app/components/hakumi_components/time_picker/component.rb +71 -17
- data/app/components/hakumi_components/timeline/component.html.erb +3 -2
- data/app/components/hakumi_components/timeline/component.rb +188 -55
- data/app/components/hakumi_components/timeline/item/component.html.erb +2 -2
- data/app/components/hakumi_components/timeline/item/component.rb +121 -60
- data/app/components/hakumi_components/tooltip/component.html.erb +1 -1
- data/app/components/hakumi_components/tooltip/component.rb +89 -64
- data/app/components/hakumi_components/tour/component.rb +74 -49
- data/app/components/hakumi_components/tour/step.rb +86 -0
- data/app/components/hakumi_components/transfer/coercion.rb +66 -0
- data/app/components/hakumi_components/transfer/component.html.erb +171 -140
- data/app/components/hakumi_components/transfer/component.rb +232 -82
- data/app/components/hakumi_components/transfer/item.rb +54 -0
- data/app/components/hakumi_components/transfer/operations.rb +31 -0
- data/app/components/hakumi_components/tree/component.rb +219 -400
- data/app/components/hakumi_components/tree/concerns/node_rendering.rb +365 -0
- data/app/components/hakumi_components/tree/node.rb +276 -0
- data/app/components/hakumi_components/tree_select/component.html.erb +23 -9
- data/app/components/hakumi_components/tree_select/component.rb +116 -107
- data/app/components/hakumi_components/typography/base_component.rb +69 -36
- data/app/components/hakumi_components/typography/link/component.rb +63 -6
- data/app/components/hakumi_components/typography/paragraph/component.rb +6 -0
- data/app/components/hakumi_components/typography/text/component.rb +4 -0
- data/app/components/hakumi_components/typography/title/component.rb +61 -6
- data/app/components/hakumi_components/upload/component.html.erb +106 -88
- data/app/components/hakumi_components/upload/component.rb +222 -48
- data/app/components/hakumi_components/upload/file_entry.rb +53 -0
- data/app/controllers/hakumi_components/components_controller.rb +2 -1
- data/app/form_builders/hakumi_components/form_builder/contracts.rb +55 -0
- data/app/form_builders/hakumi_components/form_builder/field_context.rb +43 -0
- data/app/form_builders/hakumi_components/form_builder/model_binding.rb +163 -0
- data/app/form_builders/hakumi_components/form_builder.rb +238 -317
- data/app/helpers/hakumi_components/form_helper.rb +27 -23
- data/app/javascript/hakumi_components/controllers/hakumi/admin_panel_controller.js +5 -7
- data/app/javascript/hakumi_components/controllers/hakumi/back_top_controller.js +1 -1
- data/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +108 -2
- data/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +183 -95
- data/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +23 -285
- data/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +274 -262
- data/app/javascript/hakumi_components/controllers/hakumi/float_button_group_controller.js +2 -2
- data/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +4 -2
- data/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +119 -125
- data/app/javascript/hakumi_components/controllers/hakumi/table/editable.js +291 -0
- data/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +166 -366
- data/app/javascript/hakumi_components/controllers/hakumi/tabs_controller.js +8 -2
- data/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +27 -25
- data/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +19 -18
- data/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +116 -117
- data/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +17 -1
- data/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +363 -78
- data/app/javascript/hakumi_components/controllers/hakumi/typography_controller.js +3 -3
- data/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +320 -204
- data/app/javascript/hakumi_components/core/render_component.js +37 -11
- data/app/javascript/hakumi_components/utils/color_helper.js +262 -0
- data/app/javascript/stylesheets/_base.scss +9 -0
- data/app/javascript/stylesheets/_hakumi_components.scss +1 -0
- data/app/javascript/stylesheets/components/_breadcrumb.scss +2 -2
- data/app/javascript/stylesheets/components/_calendar.scss +13 -13
- data/app/javascript/stylesheets/components/_cascader.scss +5 -5
- data/app/javascript/stylesheets/components/_checkbox.scss +9 -11
- data/app/javascript/stylesheets/components/_color_picker.scss +11 -11
- data/app/javascript/stylesheets/components/_date_picker.scss +4 -4
- data/app/javascript/stylesheets/components/_descriptions.scss +2 -2
- data/app/javascript/stylesheets/components/_drawer.scss +3 -3
- data/app/javascript/stylesheets/components/_dropdown.scss +2 -2
- data/app/javascript/stylesheets/components/_flex.scss +1 -1
- data/app/javascript/stylesheets/components/_float_button.scss +5 -5
- data/app/javascript/stylesheets/components/_form_item.scss +92 -0
- data/app/javascript/stylesheets/components/_image.scss +15 -15
- data/app/javascript/stylesheets/components/_input.scss +23 -113
- data/app/javascript/stylesheets/components/_layout.scss +27 -26
- data/app/javascript/stylesheets/components/_menu.scss +15 -15
- data/app/javascript/stylesheets/components/_modal.scss +13 -13
- data/app/javascript/stylesheets/components/_notification.scss +3 -3
- data/app/javascript/stylesheets/components/_popover.scss +1 -1
- data/app/javascript/stylesheets/components/_segmented.scss +3 -3
- data/app/javascript/stylesheets/components/_select.scss +6 -6
- data/app/javascript/stylesheets/components/_slider.scss +1 -1
- data/app/javascript/stylesheets/components/_spin.scss +2 -2
- data/app/javascript/stylesheets/components/_steps.scss +10 -10
- data/app/javascript/stylesheets/components/_switch.scss +11 -10
- data/app/javascript/stylesheets/components/_table.scss +6 -6
- data/app/javascript/stylesheets/components/_tag.scss +2 -2
- data/app/javascript/stylesheets/components/_tooltip.scss +4 -4
- data/app/javascript/stylesheets/components/_tree_select.scss +3 -3
- data/app/javascript/stylesheets/components/_typography.scss +3 -3
- data/app/javascript/stylesheets/components/_upload.scss +4 -4
- data/app/services/hakumi_components/component_handler.rb +95 -24
- data/app/services/hakumi_components/icon/loader.rb +84 -67
- data/app/services/hakumi_components/illustrations/loader.rb +35 -27
- data/app/views/hakumi/_alert.html.erb +4 -1
- data/app/views/hakumi/_drawer.html.erb +1 -1
- data/app/views/hakumi/_statistic.html.erb +11 -11
- data/lib/generators/hakumi_components/install_generator.rb +14 -1
- data/lib/hakumi_components/documentation/models.rb +290 -0
- data/lib/hakumi_components/documentation/node.rb +73 -0
- data/lib/hakumi_components/documentation.rb +139 -62
- data/lib/hakumi_components/engine.rb +17 -11
- data/lib/hakumi_components/file_size_checker.rb +215 -0
- data/lib/hakumi_components/locales/en.yml +115 -0
- data/lib/hakumi_components/rails/attribute_introspection.rb +39 -107
- data/lib/hakumi_components/rails/model_reflection.rb +154 -0
- data/lib/hakumi_components/rails/validation_introspection.rb +25 -73
- data/lib/hakumi_components/rails/validation_mapper.rb +148 -201
- data/lib/hakumi_components/rails.rb +2 -0
- data/lib/hakumi_components/stylesheet_ownership_checker.rb +191 -0
- data/lib/hakumi_components/types/form_field.rb +24 -0
- data/lib/hakumi_components/types/html.rb +28 -0
- data/lib/hakumi_components/types/rendering.rb +10 -0
- data/lib/hakumi_components/types/stimulus.rb +19 -0
- data/lib/hakumi_components/types/validation.rb +22 -0
- data/lib/hakumi_components/types.rb +13 -0
- data/lib/hakumi_components/version.rb +2 -1
- data/lib/hakumi_components.rb +26 -6
- data/lib/tasks/coverage.rake +1 -0
- metadata +100 -136
- data/sig/action_view/tag_builder.rbs +0 -24
- data/sig/active_support/concern_patch.rbs +0 -7
- data/sig/generators/hakumi_components/install_generator.rbs +0 -11
- data/sig/hakumi_components/admin_panel/component.rbs +0 -28
- data/sig/hakumi_components/affix/component.rbs +0 -44
- data/sig/hakumi_components/alert/component.rbs +0 -64
- data/sig/hakumi_components/anchor/component.rbs +0 -45
- data/sig/hakumi_components/anchor/link/component.rbs +0 -31
- data/sig/hakumi_components/autocomplete/component.rbs +0 -68
- data/sig/hakumi_components/avatar/component.rbs +0 -44
- data/sig/hakumi_components/badge/component.rbs +0 -41
- data/sig/hakumi_components/base_component.rbs +0 -82
- data/sig/hakumi_components/breadcrumb/component.rbs +0 -21
- data/sig/hakumi_components/breadcrumb/item/component.rbs +0 -36
- data/sig/hakumi_components/button/component.rbs +0 -56
- data/sig/hakumi_components/calendar/component.rbs +0 -41
- data/sig/hakumi_components/card/component.rbs +0 -50
- data/sig/hakumi_components/card/grid/component.rbs +0 -21
- data/sig/hakumi_components/card/meta/component.rbs +0 -25
- data/sig/hakumi_components/carousel/component.rbs +0 -88
- data/sig/hakumi_components/cascader/component.rbs +0 -52
- data/sig/hakumi_components/checkbox/component.rbs +0 -46
- data/sig/hakumi_components/checkbox/group/component.rbs +0 -34
- data/sig/hakumi_components/collapse/component.rbs +0 -57
- data/sig/hakumi_components/collapse/panel/component.rbs +0 -47
- data/sig/hakumi_components/color_picker/component.rbs +0 -80
- data/sig/hakumi_components/concerns/form_field.rbs +0 -89
- data/sig/hakumi_components/container/component.rbs +0 -37
- data/sig/hakumi_components/date_picker/component.rbs +0 -72
- data/sig/hakumi_components/date_picker/range_picker.rbs +0 -68
- data/sig/hakumi_components/date_picker/shared_rendering.rbs +0 -41
- data/sig/hakumi_components/descriptions/component.rbs +0 -59
- data/sig/hakumi_components/descriptions/item/component.rbs +0 -31
- data/sig/hakumi_components/divider/component.rbs +0 -39
- data/sig/hakumi_components/drawer/component.rbs +0 -66
- data/sig/hakumi_components/dropdown/component.rbs +0 -45
- data/sig/hakumi_components/dropdown/divider/component.rbs +0 -11
- data/sig/hakumi_components/dropdown/item/component.rbs +0 -45
- data/sig/hakumi_components/empty/component.rbs +0 -40
- data/sig/hakumi_components/flex/component.rbs +0 -47
- data/sig/hakumi_components/float_button/back_top/component.rbs +0 -45
- data/sig/hakumi_components/float_button/component.rbs +0 -80
- data/sig/hakumi_components/float_button/group/component.rbs +0 -95
- data/sig/hakumi_components/float_button/group_cluster/component.rbs +0 -60
- data/sig/hakumi_components/form/item/component.rbs +0 -39
- data/sig/hakumi_components/form_builder.rbs +0 -37
- data/sig/hakumi_components/grid/col/component.rbs +0 -50
- data/sig/hakumi_components/grid/row/component.rbs +0 -46
- data/sig/hakumi_components/icon/component.rbs +0 -56
- data/sig/hakumi_components/image/component.rbs +0 -56
- data/sig/hakumi_components/image/preview_group/component.rbs +0 -28
- data/sig/hakumi_components/input/component.rbs +0 -71
- data/sig/hakumi_components/input/password/component.rbs +0 -27
- data/sig/hakumi_components/input/text_area/component.rbs +0 -42
- data/sig/hakumi_components/input_number/component.rbs +0 -85
- data/sig/hakumi_components/layout/component.rbs +0 -21
- data/sig/hakumi_components/layout/content/component.rbs +0 -19
- data/sig/hakumi_components/layout/footer/component.rbs +0 -19
- data/sig/hakumi_components/layout/header/component.rbs +0 -19
- data/sig/hakumi_components/layout/sider/component.rbs +0 -19
- data/sig/hakumi_components/mentions/component.rbs +0 -75
- data/sig/hakumi_components/menu/component.rbs +0 -52
- data/sig/hakumi_components/menu/divider/component.rbs +0 -11
- data/sig/hakumi_components/menu/group/component.rbs +0 -20
- data/sig/hakumi_components/menu/item/component.rbs +0 -42
- data/sig/hakumi_components/menu/sub_menu/component.rbs +0 -37
- data/sig/hakumi_components/message/component.rbs +0 -50
- data/sig/hakumi_components/modal/component.rbs +0 -76
- data/sig/hakumi_components/modal/confirm/component.rbs +0 -32
- data/sig/hakumi_components/modal/error/component.rbs +0 -27
- data/sig/hakumi_components/modal/info/component.rbs +0 -27
- data/sig/hakumi_components/modal/success/component.rbs +0 -27
- data/sig/hakumi_components/modal/warning/component.rbs +0 -27
- data/sig/hakumi_components/notification/component.rbs +0 -64
- data/sig/hakumi_components/pagination/component.rbs +0 -113
- data/sig/hakumi_components/popconfirm/component.rbs +0 -52
- data/sig/hakumi_components/popover/component.rbs +0 -66
- data/sig/hakumi_components/progress/component.rbs +0 -201
- data/sig/hakumi_components/qr_code/component.rbs +0 -111
- data/sig/hakumi_components/radio/component.rbs +0 -55
- data/sig/hakumi_components/radio/group/component.rbs +0 -71
- data/sig/hakumi_components/rails/attribute_introspection.rbs +0 -16
- data/sig/hakumi_components/rails/validation_introspection.rbs +0 -18
- data/sig/hakumi_components/rails/validation_mapper.rbs +0 -53
- data/sig/hakumi_components/rails.rbs +0 -6
- data/sig/hakumi_components/rate/component.rbs +0 -56
- data/sig/hakumi_components/result/component.rbs +0 -65
- data/sig/hakumi_components/segmented/component.rbs +0 -72
- data/sig/hakumi_components/select/component.rbs +0 -73
- data/sig/hakumi_components/skeleton/avatar/component.rbs +0 -23
- data/sig/hakumi_components/skeleton/button/component.rbs +0 -24
- data/sig/hakumi_components/skeleton/component.rbs +0 -76
- data/sig/hakumi_components/skeleton/image/component.rbs +0 -22
- data/sig/hakumi_components/skeleton/input/component.rbs +0 -23
- data/sig/hakumi_components/skeleton/node/component.rbs +0 -21
- data/sig/hakumi_components/slider/component.rbs +0 -85
- data/sig/hakumi_components/space/compact/component.rbs +0 -21
- data/sig/hakumi_components/space/component.rbs +0 -47
- data/sig/hakumi_components/spin/component.rbs +0 -77
- data/sig/hakumi_components/splitter/component.rbs +0 -27
- data/sig/hakumi_components/splitter/panel/component.rbs +0 -36
- data/sig/hakumi_components/statistic/component.rbs +0 -97
- data/sig/hakumi_components/steps/component.rbs +0 -82
- data/sig/hakumi_components/steps/item/component.rbs +0 -29
- data/sig/hakumi_components/switch/component.rbs +0 -52
- data/sig/hakumi_components/table/column/component.rbs +0 -57
- data/sig/hakumi_components/table/column_group/component.rbs +0 -20
- data/sig/hakumi_components/table/component.rbs +0 -279
- data/sig/hakumi_components/table/concerns/columns.rbs +0 -95
- data/sig/hakumi_components/table/concerns/editable.rbs +0 -40
- data/sig/hakumi_components/table/concerns/ellipsis.rbs +0 -27
- data/sig/hakumi_components/table/concerns/fixed_columns.rbs +0 -33
- data/sig/hakumi_components/tabs/component.rbs +0 -66
- data/sig/hakumi_components/tabs/item/component.rbs +0 -29
- data/sig/hakumi_components/tag/component.rbs +0 -86
- data/sig/hakumi_components/tag/group/component.rbs +0 -24
- data/sig/hakumi_components/time_picker/component.rbs +0 -72
- data/sig/hakumi_components/timeline/component.rbs +0 -61
- data/sig/hakumi_components/timeline/item/component.rbs +0 -70
- data/sig/hakumi_components/tooltip/component.rbs +0 -73
- data/sig/hakumi_components/tour/component.rbs +0 -84
- data/sig/hakumi_components/transfer/component.rbs +0 -75
- data/sig/hakumi_components/tree/component.rbs +0 -126
- data/sig/hakumi_components/tree_select/component.rbs +0 -122
- data/sig/hakumi_components/typography/base_component.rbs +0 -57
- data/sig/hakumi_components/typography/link/component.rbs +0 -28
- data/sig/hakumi_components/typography/paragraph/component.rbs +0 -13
- data/sig/hakumi_components/typography/text/component.rbs +0 -10
- data/sig/hakumi_components/typography/title/component.rbs +0 -28
- data/sig/hakumi_components/upload/component.rbs +0 -78
- data/sig/hakumi_components.rbs +0 -96
- data/sig/rails/active_model/validations/comparison_validator.rbs +0 -6
- data/sig/rails.rbs +0 -18
- data/sig/view_component/base.rbs +0 -28
|
@@ -5,6 +5,78 @@ const ALLOWED_URL_PROTOCOLS = ["http:", "https:", "data:"]
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const VALID_STATUSES = ["ready", "uploading", "done", "error", "aborted"]
|
|
8
|
+
const BLOCKED_UPLOAD_HEADERS = ["cookie", "host", "origin", "referer"]
|
|
9
|
+
const IMAGE_DATA_URL_PREFIX = "data:image/"
|
|
10
|
+
const MAX_IMAGE_DATA_URL_LENGTH = 1024 * 100
|
|
11
|
+
|
|
12
|
+
const generateUploadUid = () => `upload_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
|
13
|
+
|
|
14
|
+
const sanitizeUploadFileName = (name) => {
|
|
15
|
+
if (typeof name !== "string") return "file"
|
|
16
|
+
|
|
17
|
+
return name
|
|
18
|
+
.replace(/[<>:"\/\\|?*\x00-\x1f]/g, "_")
|
|
19
|
+
.slice(0, 255)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sanitizeUploadUrl = (url) => {
|
|
23
|
+
if (!url || typeof url !== "string") return null
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
if (url.startsWith("data:")) {
|
|
27
|
+
if (url.startsWith(IMAGE_DATA_URL_PREFIX)) {
|
|
28
|
+
return url.slice(0, MAX_IMAGE_DATA_URL_LENGTH)
|
|
29
|
+
}
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parsed = new URL(url)
|
|
34
|
+
if (ALLOWED_URL_PROTOCOLS.includes(parsed.protocol)) {
|
|
35
|
+
return parsed.href
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const normalizeUploadStatus = (status) => {
|
|
44
|
+
if (typeof status !== "string") return null
|
|
45
|
+
const normalized = status.toLowerCase()
|
|
46
|
+
return VALID_STATUSES.includes(normalized) ? normalized : null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const clampUploadPercent = (percent) => {
|
|
50
|
+
const num = Number(percent)
|
|
51
|
+
if (isNaN(num)) return 0
|
|
52
|
+
return Math.max(0, Math.min(100, Math.round(num)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const isBlockedUploadHeader = (header) => BLOCKED_UPLOAD_HEADERS.includes(header.toLowerCase())
|
|
56
|
+
|
|
57
|
+
const normalizeAcceptTypes = (accept) => accept.split(",").map((type) => type.trim().toLowerCase())
|
|
58
|
+
|
|
59
|
+
const normalizeFileTypeMatchInput = (file) => ({
|
|
60
|
+
name: file.name.toLowerCase(),
|
|
61
|
+
type: file.type.toLowerCase()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const isPreviewRecord = (record) => record.status === "done" && record.url
|
|
65
|
+
|
|
66
|
+
const clearUploadXhr = (record, xhr) => {
|
|
67
|
+
if (record.xhr === xhr) delete record.xhr
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const releaseUploadRecordResources = (record) => {
|
|
71
|
+
if (record.xhr) record.xhr.abort()
|
|
72
|
+
if (record.blobUrl && record.url) URL.revokeObjectURL(record.url)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const normalizePreviewItem = (record) => ({
|
|
76
|
+
src: record.url,
|
|
77
|
+
alt: record.name,
|
|
78
|
+
uid: record.uid
|
|
79
|
+
})
|
|
8
80
|
|
|
9
81
|
export default class extends RegistryController {
|
|
10
82
|
static targets = ["input", "list", "dropzone", "inlineTrigger", "avatar", "avatarWrapper"]
|
|
@@ -28,11 +100,18 @@ export default class extends RegistryController {
|
|
|
28
100
|
inlineTrigger: { type: Boolean, default: false },
|
|
29
101
|
avatarShape: String,
|
|
30
102
|
avatarSize: Number,
|
|
31
|
-
avatarIcon: String
|
|
103
|
+
avatarIcon: String,
|
|
104
|
+
// i18n
|
|
105
|
+
previewLabel: { type: String, default: "Preview" },
|
|
106
|
+
removeLabel: { type: String, default: "Remove" },
|
|
107
|
+
closeLabel: { type: String, default: "Close" },
|
|
108
|
+
previousLabel: { type: String, default: "Previous" },
|
|
109
|
+
nextLabel: { type: String, default: "Next" }
|
|
32
110
|
}
|
|
33
111
|
|
|
34
112
|
|
|
35
113
|
setup() {
|
|
114
|
+
this.activeAvatarUploadRecord = null
|
|
36
115
|
this.fileList = this.normalizeFileList(this.defaultFileListValue || [])
|
|
37
116
|
this.renderFileList()
|
|
38
117
|
}
|
|
@@ -40,14 +119,10 @@ export default class extends RegistryController {
|
|
|
40
119
|
|
|
41
120
|
teardown() {
|
|
42
121
|
|
|
43
|
-
this
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (record.blobUrl && record.url) {
|
|
48
|
-
URL.revokeObjectURL(record.url)
|
|
49
|
-
}
|
|
50
|
-
})
|
|
122
|
+
this.#closePreviewModal({ animate: false })
|
|
123
|
+
|
|
124
|
+
this.releaseActiveAvatarUploadRecord()
|
|
125
|
+
this.fileList.forEach(releaseUploadRecordResources)
|
|
51
126
|
}
|
|
52
127
|
|
|
53
128
|
|
|
@@ -91,15 +166,7 @@ export default class extends RegistryController {
|
|
|
91
166
|
addFile(fileData) {
|
|
92
167
|
if (!fileData || typeof fileData !== "object") return null
|
|
93
168
|
|
|
94
|
-
const record =
|
|
95
|
-
uid: `upload_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
|
96
|
-
name: this.sanitizeFileName(fileData.name || "file"),
|
|
97
|
-
status: this.validateStatus(fileData.status) || "ready",
|
|
98
|
-
url: this.sanitizeUrl(fileData.url),
|
|
99
|
-
percent: this.clampPercent(fileData.percent || 0),
|
|
100
|
-
size: typeof fileData.size === "number" ? fileData.size : null,
|
|
101
|
-
error: typeof fileData.error === "string" ? fileData.error : null
|
|
102
|
-
}
|
|
169
|
+
const record = this.normalizeApiFileData(fileData)
|
|
103
170
|
|
|
104
171
|
|
|
105
172
|
if (this.hasMaxCountValue && this.fileList.length >= this.maxCountValue) {
|
|
@@ -205,54 +272,40 @@ export default class extends RegistryController {
|
|
|
205
272
|
|
|
206
273
|
|
|
207
274
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.
|
|
213
|
-
.
|
|
275
|
+
normalizeApiFileData(fileData) {
|
|
276
|
+
return {
|
|
277
|
+
uid: generateUploadUid(),
|
|
278
|
+
name: this.sanitizeFileName(fileData.name || "file"),
|
|
279
|
+
status: this.validateStatus(fileData.status) || "ready",
|
|
280
|
+
url: this.sanitizeUrl(fileData.url),
|
|
281
|
+
percent: this.clampPercent(fileData.percent || 0),
|
|
282
|
+
size: typeof fileData.size === "number" ? fileData.size : null,
|
|
283
|
+
error: typeof fileData.error === "string" ? fileData.error : null
|
|
284
|
+
}
|
|
214
285
|
}
|
|
215
286
|
|
|
216
287
|
|
|
217
288
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
try {
|
|
289
|
+
sanitizeFileName(name) {
|
|
290
|
+
return sanitizeUploadFileName(name)
|
|
291
|
+
}
|
|
222
292
|
|
|
223
|
-
if (url.startsWith("data:")) {
|
|
224
293
|
|
|
225
|
-
if (url.startsWith("data:image/")) {
|
|
226
|
-
return url.slice(0, 1024 * 100)
|
|
227
|
-
}
|
|
228
|
-
return null
|
|
229
|
-
}
|
|
230
294
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return parsed.href
|
|
234
|
-
}
|
|
235
|
-
} catch {
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return null
|
|
295
|
+
sanitizeUrl(url) {
|
|
296
|
+
return sanitizeUploadUrl(url)
|
|
240
297
|
}
|
|
241
298
|
|
|
242
299
|
|
|
243
300
|
|
|
244
301
|
validateStatus(status) {
|
|
245
|
-
|
|
246
|
-
const normalized = status.toLowerCase()
|
|
247
|
-
return VALID_STATUSES.includes(normalized) ? normalized : null
|
|
302
|
+
return normalizeUploadStatus(status)
|
|
248
303
|
}
|
|
249
304
|
|
|
250
305
|
|
|
251
306
|
|
|
252
307
|
clampPercent(percent) {
|
|
253
|
-
|
|
254
|
-
if (isNaN(num)) return 0
|
|
255
|
-
return Math.max(0, Math.min(100, Math.round(num)))
|
|
308
|
+
return clampUploadPercent(percent)
|
|
256
309
|
}
|
|
257
310
|
|
|
258
311
|
|
|
@@ -260,21 +313,32 @@ export default class extends RegistryController {
|
|
|
260
313
|
validateFileType(file) {
|
|
261
314
|
if (!this.acceptValue) return true
|
|
262
315
|
|
|
263
|
-
const acceptTypes = this.acceptValue
|
|
264
|
-
const
|
|
265
|
-
const fileType = file.type.toLowerCase()
|
|
316
|
+
const acceptTypes = normalizeAcceptTypes(this.acceptValue)
|
|
317
|
+
const fileMatch = normalizeFileTypeMatchInput(file)
|
|
266
318
|
|
|
267
319
|
return acceptTypes.some((accept) => {
|
|
268
320
|
if (accept.startsWith(".")) {
|
|
269
|
-
return
|
|
321
|
+
return fileMatch.name.endsWith(accept)
|
|
270
322
|
} else if (accept.endsWith("/*")) {
|
|
271
|
-
return
|
|
323
|
+
return fileMatch.type.startsWith(accept.slice(0, -2))
|
|
272
324
|
} else {
|
|
273
|
-
return
|
|
325
|
+
return fileMatch.type === accept
|
|
274
326
|
}
|
|
275
327
|
})
|
|
276
328
|
}
|
|
277
329
|
|
|
330
|
+
validationErrorForFile(file, { avatar = false } = {}) {
|
|
331
|
+
if (!this.validateFileType(file)) {
|
|
332
|
+
return avatar ? "Please upload an image file" : "File type not accepted"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (this.hasMaxSizeValue && file.size > this.maxSizeValue) {
|
|
336
|
+
return "File exceeds max size"
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
|
|
278
342
|
getCsrfToken() {
|
|
279
343
|
const meta = document.querySelector('meta[name="csrf-token"]')
|
|
280
344
|
return meta ? meta.getAttribute("content") : null
|
|
@@ -335,20 +399,11 @@ export default class extends RegistryController {
|
|
|
335
399
|
|
|
336
400
|
acceptedFiles.forEach((file) => {
|
|
337
401
|
const record = this.buildFileRecord(file)
|
|
402
|
+
const validationError = this.validationErrorForFile(file)
|
|
338
403
|
|
|
339
|
-
|
|
340
|
-
if (!this.validateFileType(file)) {
|
|
341
|
-
record.status = "error"
|
|
342
|
-
record.error = "File type not accepted"
|
|
343
|
-
this.fileList.push(record)
|
|
344
|
-
this.dispatchError(record)
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (this.hasMaxSizeValue && file.size > this.maxSizeValue) {
|
|
404
|
+
if (validationError) {
|
|
350
405
|
record.status = "error"
|
|
351
|
-
record.error =
|
|
406
|
+
record.error = validationError
|
|
352
407
|
this.fileList.push(record)
|
|
353
408
|
this.dispatchError(record)
|
|
354
409
|
} else {
|
|
@@ -388,14 +443,7 @@ export default class extends RegistryController {
|
|
|
388
443
|
if (!record) return
|
|
389
444
|
|
|
390
445
|
|
|
391
|
-
|
|
392
|
-
record.xhr.abort()
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (record.blobUrl && record.url) {
|
|
397
|
-
URL.revokeObjectURL(record.url)
|
|
398
|
-
}
|
|
446
|
+
releaseUploadRecordResources(record)
|
|
399
447
|
|
|
400
448
|
this.fileList = this.fileList.filter((item) => item.uid !== record.uid)
|
|
401
449
|
this.renderFileList()
|
|
@@ -414,10 +462,7 @@ export default class extends RegistryController {
|
|
|
414
462
|
|
|
415
463
|
removeAll() {
|
|
416
464
|
|
|
417
|
-
this.fileList.forEach(
|
|
418
|
-
if (record.xhr) record.xhr.abort()
|
|
419
|
-
if (record.blobUrl && record.url) URL.revokeObjectURL(record.url)
|
|
420
|
-
})
|
|
465
|
+
this.fileList.forEach(releaseUploadRecordResources)
|
|
421
466
|
|
|
422
467
|
this.fileList = []
|
|
423
468
|
this.renderFileList()
|
|
@@ -431,10 +476,7 @@ export default class extends RegistryController {
|
|
|
431
476
|
|
|
432
477
|
setFileList(list) {
|
|
433
478
|
|
|
434
|
-
this.fileList.forEach(
|
|
435
|
-
if (record.xhr) record.xhr.abort()
|
|
436
|
-
if (record.blobUrl && record.url) URL.revokeObjectURL(record.url)
|
|
437
|
-
})
|
|
479
|
+
this.fileList.forEach(releaseUploadRecordResources)
|
|
438
480
|
|
|
439
481
|
this.fileList = this.normalizeFileList(list || [])
|
|
440
482
|
this.renderFileList()
|
|
@@ -444,7 +486,9 @@ export default class extends RegistryController {
|
|
|
444
486
|
abort(file) {
|
|
445
487
|
const record = this.resolveRecord(file)
|
|
446
488
|
if (!record || !record.xhr) return
|
|
447
|
-
record
|
|
489
|
+
const { xhr } = record
|
|
490
|
+
xhr.abort()
|
|
491
|
+
clearUploadXhr(record, xhr)
|
|
448
492
|
record.status = "aborted"
|
|
449
493
|
this.renderFileList()
|
|
450
494
|
this.dispatchError(record, "aborted")
|
|
@@ -453,7 +497,11 @@ export default class extends RegistryController {
|
|
|
453
497
|
|
|
454
498
|
abortAll() {
|
|
455
499
|
this.fileList.forEach((record) => {
|
|
456
|
-
if (record.xhr)
|
|
500
|
+
if (record.xhr) {
|
|
501
|
+
const { xhr } = record
|
|
502
|
+
xhr.abort()
|
|
503
|
+
clearUploadXhr(record, xhr)
|
|
504
|
+
}
|
|
457
505
|
if (record.status === "uploading") record.status = "aborted"
|
|
458
506
|
})
|
|
459
507
|
this.renderFileList()
|
|
@@ -480,9 +528,7 @@ export default class extends RegistryController {
|
|
|
480
528
|
record.xhr = xhr
|
|
481
529
|
|
|
482
530
|
|
|
483
|
-
|
|
484
|
-
xhr.withCredentials = true
|
|
485
|
-
}
|
|
531
|
+
this.applyUploadRequestOptions(xhr)
|
|
486
532
|
|
|
487
533
|
xhr.upload.addEventListener("progress", (event) => {
|
|
488
534
|
if (!event.lengthComputable) return
|
|
@@ -492,6 +538,8 @@ export default class extends RegistryController {
|
|
|
492
538
|
})
|
|
493
539
|
|
|
494
540
|
xhr.addEventListener("load", () => {
|
|
541
|
+
clearUploadXhr(record, xhr)
|
|
542
|
+
|
|
495
543
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
496
544
|
record.status = "done"
|
|
497
545
|
record.percent = 100
|
|
@@ -513,6 +561,7 @@ export default class extends RegistryController {
|
|
|
513
561
|
})
|
|
514
562
|
|
|
515
563
|
xhr.addEventListener("error", () => {
|
|
564
|
+
clearUploadXhr(record, xhr)
|
|
516
565
|
record.status = "error"
|
|
517
566
|
record.error = "Network error"
|
|
518
567
|
this.renderFileList()
|
|
@@ -521,32 +570,43 @@ export default class extends RegistryController {
|
|
|
521
570
|
})
|
|
522
571
|
|
|
523
572
|
xhr.addEventListener("abort", () => {
|
|
573
|
+
clearUploadXhr(record, xhr)
|
|
524
574
|
record.status = "aborted"
|
|
525
575
|
this.renderFileList()
|
|
526
576
|
this.dispatchError(record, "aborted")
|
|
527
577
|
this.dispatchChange(record)
|
|
528
578
|
})
|
|
529
579
|
|
|
530
|
-
|
|
580
|
+
this.sendUploadRequest(xhr, record.raw)
|
|
581
|
+
}
|
|
531
582
|
|
|
583
|
+
applyUploadRequestOptions(xhr) {
|
|
584
|
+
if (this.withCredentialsValue) {
|
|
585
|
+
xhr.withCredentials = true
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
openUploadRequest(xhr) {
|
|
590
|
+
xhr.open("POST", this.actionValue)
|
|
532
591
|
|
|
533
592
|
const csrfToken = this.getCsrfToken()
|
|
534
593
|
if (csrfToken) {
|
|
535
594
|
xhr.setRequestHeader("X-CSRF-Token", csrfToken)
|
|
536
595
|
}
|
|
537
596
|
|
|
538
|
-
|
|
539
597
|
const headers = this.ensureObject(this.headersValue)
|
|
540
598
|
if (headers) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const lowerKey = key.toLowerCase()
|
|
544
|
-
if (!["cookie", "host", "origin", "referer"].includes(lowerKey)) {
|
|
545
|
-
xhr.setRequestHeader(key, value)
|
|
546
|
-
}
|
|
599
|
+
this.getSafeUploadHeaders(headers).forEach(([key, value]) => {
|
|
600
|
+
xhr.setRequestHeader(key, value)
|
|
547
601
|
})
|
|
548
602
|
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
getSafeUploadHeaders(headers) {
|
|
606
|
+
return Object.entries(headers).filter(([key]) => !isBlockedUploadHeader(key))
|
|
607
|
+
}
|
|
549
608
|
|
|
609
|
+
buildUploadFormData(file) {
|
|
550
610
|
const formData = new FormData()
|
|
551
611
|
const extraData = this.ensureObject(this.dataValue)
|
|
552
612
|
if (extraData) {
|
|
@@ -555,33 +615,38 @@ export default class extends RegistryController {
|
|
|
555
615
|
})
|
|
556
616
|
}
|
|
557
617
|
|
|
558
|
-
formData.append(this.nameValue || "file",
|
|
559
|
-
|
|
618
|
+
formData.append(this.nameValue || "file", file)
|
|
619
|
+
return formData
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
sendUploadRequest(xhr, file) {
|
|
623
|
+
this.openUploadRequest(xhr)
|
|
624
|
+
xhr.send(this.buildUploadFormData(file))
|
|
560
625
|
}
|
|
561
626
|
|
|
562
627
|
normalizeFileList(list) {
|
|
563
|
-
return (list || []).map((item, index) =>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const record = {
|
|
567
|
-
uid: item.uid || `upload_${Date.now()}_${index}`,
|
|
568
|
-
name: this.sanitizeFileName(item.name || item.filename || "file"),
|
|
569
|
-
status: this.validateStatus(item.status) || "ready",
|
|
570
|
-
url: this.sanitizeUrl(item.url),
|
|
571
|
-
percent: this.clampPercent(item.percent || 0),
|
|
572
|
-
size: item.size,
|
|
573
|
-
response: item.response,
|
|
574
|
-
error: typeof item.error === "string" ? item.error : null,
|
|
575
|
-
raw: item.raw || item.file
|
|
576
|
-
}
|
|
628
|
+
return (list || []).map((item, index) => this.normalizeFileListItem(item, index))
|
|
629
|
+
}
|
|
577
630
|
|
|
578
|
-
|
|
579
|
-
|
|
631
|
+
normalizeFileListItem(item, index) {
|
|
632
|
+
if (item instanceof File) return this.buildFileRecord(item)
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
uid: item.uid || `upload_${Date.now()}_${index}`,
|
|
636
|
+
name: this.sanitizeFileName(item.name || item.filename || "file"),
|
|
637
|
+
status: this.validateStatus(item.status) || "ready",
|
|
638
|
+
url: this.sanitizeUrl(item.url),
|
|
639
|
+
percent: this.clampPercent(item.percent || 0),
|
|
640
|
+
size: item.size,
|
|
641
|
+
response: item.response,
|
|
642
|
+
error: typeof item.error === "string" ? item.error : null,
|
|
643
|
+
raw: item.raw || item.file
|
|
644
|
+
}
|
|
580
645
|
}
|
|
581
646
|
|
|
582
647
|
buildFileRecord(file) {
|
|
583
648
|
const record = {
|
|
584
|
-
uid:
|
|
649
|
+
uid: generateUploadUid(),
|
|
585
650
|
name: this.sanitizeFileName(file.name),
|
|
586
651
|
status: "ready",
|
|
587
652
|
percent: 0,
|
|
@@ -675,7 +740,7 @@ export default class extends RegistryController {
|
|
|
675
740
|
viewButton.type = "button"
|
|
676
741
|
viewButton.className = "hakumi-upload-list-item-action hakumi-upload-list-item-view"
|
|
677
742
|
viewButton.innerHTML = `<svg viewBox="64 64 896 896" width="1em" height="1em" fill="currentColor"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"/></svg>`
|
|
678
|
-
viewButton.title =
|
|
743
|
+
viewButton.title = this.previewLabelValue
|
|
679
744
|
viewButton.addEventListener("click", (e) => {
|
|
680
745
|
e.stopPropagation()
|
|
681
746
|
this.openPreview(record)
|
|
@@ -687,7 +752,7 @@ export default class extends RegistryController {
|
|
|
687
752
|
removeButton.type = "button"
|
|
688
753
|
removeButton.className = "hakumi-upload-list-item-action hakumi-upload-list-item-remove"
|
|
689
754
|
removeButton.innerHTML = `<svg viewBox="64 64 896 896" width="1em" height="1em" fill="currentColor"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"/></svg>`
|
|
690
|
-
removeButton.title =
|
|
755
|
+
removeButton.title = this.removeLabelValue
|
|
691
756
|
removeButton.addEventListener("click", (e) => {
|
|
692
757
|
e.stopPropagation()
|
|
693
758
|
this.remove(record.uid)
|
|
@@ -702,7 +767,7 @@ export default class extends RegistryController {
|
|
|
702
767
|
removeButton.type = "button"
|
|
703
768
|
removeButton.className = "hakumi-upload-list-item-action hakumi-upload-list-item-remove"
|
|
704
769
|
removeButton.textContent = "×"
|
|
705
|
-
removeButton.title =
|
|
770
|
+
removeButton.title = this.removeLabelValue
|
|
706
771
|
removeButton.addEventListener("click", (e) => {
|
|
707
772
|
e.stopPropagation()
|
|
708
773
|
this.remove(record.uid)
|
|
@@ -808,42 +873,21 @@ export default class extends RegistryController {
|
|
|
808
873
|
handleAvatarUpload(file) {
|
|
809
874
|
if (!this.hasAvatarTarget || !this.hasAvatarWrapperTarget) return
|
|
810
875
|
|
|
811
|
-
|
|
812
|
-
if (
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
params: {
|
|
816
|
-
id: "hakumi-upload-avatar-message",
|
|
817
|
-
type: "error",
|
|
818
|
-
message: "Please upload an image file",
|
|
819
|
-
duration: 3
|
|
820
|
-
}
|
|
821
|
-
})
|
|
822
|
-
}
|
|
876
|
+
const validationError = this.validationErrorForFile(file, { avatar: true })
|
|
877
|
+
if (validationError) {
|
|
878
|
+
const record = this.buildAvatarErrorRecord(file, validationError)
|
|
879
|
+
this.dispatchError(record, record.error)
|
|
823
880
|
return
|
|
824
881
|
}
|
|
825
882
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
id: "hakumi-upload-avatar-message",
|
|
832
|
-
type: "error",
|
|
833
|
-
message: "File exceeds max size",
|
|
834
|
-
duration: 3
|
|
835
|
-
}
|
|
836
|
-
})
|
|
837
|
-
}
|
|
838
|
-
return
|
|
883
|
+
const previousAvatarState = this.captureAvatarState()
|
|
884
|
+
const previousAvatarRecord = this.activeAvatarUploadRecord
|
|
885
|
+
const releasePreviousAvatarOnSuccess = previousAvatarRecord?.status === "done"
|
|
886
|
+
if (!releasePreviousAvatarOnSuccess) {
|
|
887
|
+
this.releaseActiveAvatarUploadRecord(previousAvatarRecord)
|
|
839
888
|
}
|
|
840
889
|
|
|
841
|
-
|
|
842
|
-
this.avatarWrapperTarget.classList.add("hakumi-upload-avatar-wrapper-loading")
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const loadingIcon = `<span class="anticon anticon-loading" style="animation: spin 1s linear infinite;"><svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span>`
|
|
846
|
-
this.avatarTarget.innerHTML = loadingIcon
|
|
890
|
+
this.showAvatarLoading()
|
|
847
891
|
|
|
848
892
|
|
|
849
893
|
const previewUrl = URL.createObjectURL(file)
|
|
@@ -852,11 +896,19 @@ export default class extends RegistryController {
|
|
|
852
896
|
const record = this.buildFileRecord(file)
|
|
853
897
|
record.url = previewUrl
|
|
854
898
|
record.blobUrl = true
|
|
899
|
+
this.activeAvatarUploadRecord = record
|
|
855
900
|
|
|
856
901
|
|
|
857
902
|
const isDemoMode = !this.actionValue || this.actionValue === "#"
|
|
858
903
|
|
|
859
904
|
const updateAvatar = () => {
|
|
905
|
+
if (this.activeAvatarUploadRecord !== record) return
|
|
906
|
+
|
|
907
|
+
if (releasePreviousAvatarOnSuccess) {
|
|
908
|
+
releaseUploadRecordResources(previousAvatarRecord)
|
|
909
|
+
}
|
|
910
|
+
record.status = "done"
|
|
911
|
+
record.percent = 100
|
|
860
912
|
this.avatarWrapperTarget.classList.remove("hakumi-upload-avatar-wrapper-loading")
|
|
861
913
|
|
|
862
914
|
|
|
@@ -877,20 +929,28 @@ export default class extends RegistryController {
|
|
|
877
929
|
setTimeout(updateAvatar, 1500)
|
|
878
930
|
} else {
|
|
879
931
|
|
|
880
|
-
this.performAvatarUpload(record, updateAvatar)
|
|
932
|
+
this.performAvatarUpload(record, updateAvatar, () => {
|
|
933
|
+
this.restoreFailedAvatarUpload(record, previousAvatarState, previousAvatarRecord)
|
|
934
|
+
})
|
|
881
935
|
}
|
|
882
936
|
}
|
|
883
937
|
|
|
938
|
+
showAvatarLoading() {
|
|
939
|
+
this.avatarWrapperTarget.classList.add("hakumi-upload-avatar-wrapper-loading")
|
|
940
|
+
const loadingIcon = `<span class="anticon anticon-loading" style="animation: spin 1s linear infinite;"><svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span>`
|
|
941
|
+
this.avatarTarget.innerHTML = loadingIcon
|
|
942
|
+
}
|
|
943
|
+
|
|
884
944
|
|
|
885
945
|
|
|
886
|
-
performAvatarUpload(record, onSuccess) {
|
|
946
|
+
performAvatarUpload(record, onSuccess, onError = () => this.clearAvatarErrorState()) {
|
|
887
947
|
const xhr = new XMLHttpRequest()
|
|
948
|
+
record.xhr = xhr
|
|
888
949
|
|
|
889
|
-
|
|
890
|
-
xhr.withCredentials = true
|
|
891
|
-
}
|
|
950
|
+
this.applyUploadRequestOptions(xhr)
|
|
892
951
|
|
|
893
952
|
xhr.addEventListener("load", () => {
|
|
953
|
+
clearUploadXhr(record, xhr)
|
|
894
954
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
895
955
|
try {
|
|
896
956
|
record.response = JSON.parse(xhr.response)
|
|
@@ -899,43 +959,67 @@ export default class extends RegistryController {
|
|
|
899
959
|
}
|
|
900
960
|
onSuccess()
|
|
901
961
|
} else {
|
|
902
|
-
|
|
962
|
+
onError()
|
|
963
|
+
record.status = "error"
|
|
964
|
+
record.error = xhr.statusText || "Upload failed"
|
|
903
965
|
this.dispatchError(record, xhr.statusText || "Upload failed")
|
|
904
966
|
}
|
|
905
967
|
})
|
|
906
968
|
|
|
907
969
|
xhr.addEventListener("error", () => {
|
|
908
|
-
|
|
970
|
+
clearUploadXhr(record, xhr)
|
|
971
|
+
onError()
|
|
972
|
+
record.status = "error"
|
|
973
|
+
record.error = "Network error"
|
|
909
974
|
this.dispatchError(record, "Network error")
|
|
910
975
|
})
|
|
911
976
|
|
|
912
|
-
|
|
977
|
+
this.sendUploadRequest(xhr, record.raw)
|
|
978
|
+
}
|
|
913
979
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
980
|
+
buildAvatarErrorRecord(file, error) {
|
|
981
|
+
const record = this.buildFileRecord(file)
|
|
982
|
+
record.status = "error"
|
|
983
|
+
record.error = error
|
|
984
|
+
return record
|
|
985
|
+
}
|
|
918
986
|
|
|
919
|
-
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
})
|
|
987
|
+
captureAvatarState() {
|
|
988
|
+
if (!this.hasAvatarTarget) return { html: "", image: false }
|
|
989
|
+
|
|
990
|
+
return {
|
|
991
|
+
html: this.avatarTarget.innerHTML,
|
|
992
|
+
image: this.avatarTarget.classList.contains("hakumi-avatar-image")
|
|
927
993
|
}
|
|
994
|
+
}
|
|
928
995
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
996
|
+
restoreAvatarState(state) {
|
|
997
|
+
this.avatarWrapperTarget?.classList.remove("hakumi-upload-avatar-wrapper-loading")
|
|
998
|
+
if (!this.hasAvatarTarget) return
|
|
999
|
+
|
|
1000
|
+
this.avatarTarget.innerHTML = state?.html || ""
|
|
1001
|
+
this.avatarTarget.classList.toggle("hakumi-avatar-image", Boolean(state?.image))
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
restoreFailedAvatarUpload(record, previousAvatarState, previousAvatarRecord = null) {
|
|
1005
|
+
this.restoreAvatarState(previousAvatarState)
|
|
1006
|
+
this.releaseActiveAvatarUploadRecord(record)
|
|
1007
|
+
if (previousAvatarRecord?.status === "done") {
|
|
1008
|
+
this.activeAvatarUploadRecord = previousAvatarRecord
|
|
935
1009
|
}
|
|
1010
|
+
}
|
|
936
1011
|
|
|
937
|
-
|
|
938
|
-
|
|
1012
|
+
clearAvatarErrorState() {
|
|
1013
|
+
this.restoreAvatarState({ html: "", image: false })
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
releaseActiveAvatarUploadRecord(record = this.activeAvatarUploadRecord) {
|
|
1017
|
+
if (!record) return
|
|
1018
|
+
|
|
1019
|
+
releaseUploadRecordResources(record)
|
|
1020
|
+
if (this.activeAvatarUploadRecord === record) {
|
|
1021
|
+
this.activeAvatarUploadRecord = null
|
|
1022
|
+
}
|
|
939
1023
|
}
|
|
940
1024
|
|
|
941
1025
|
|
|
@@ -946,12 +1030,8 @@ export default class extends RegistryController {
|
|
|
946
1030
|
|
|
947
1031
|
getPreviewItems() {
|
|
948
1032
|
return this.fileList
|
|
949
|
-
.filter(
|
|
950
|
-
.map(
|
|
951
|
-
src: record.url,
|
|
952
|
-
alt: record.name,
|
|
953
|
-
uid: record.uid
|
|
954
|
-
}))
|
|
1033
|
+
.filter(isPreviewRecord)
|
|
1034
|
+
.map(normalizePreviewItem)
|
|
955
1035
|
}
|
|
956
1036
|
|
|
957
1037
|
|
|
@@ -971,17 +1051,30 @@ export default class extends RegistryController {
|
|
|
971
1051
|
#previewState = {
|
|
972
1052
|
modal: null,
|
|
973
1053
|
items: [],
|
|
974
|
-
currentIndex: 0
|
|
1054
|
+
currentIndex: 0,
|
|
1055
|
+
closeTimer: null
|
|
975
1056
|
}
|
|
976
1057
|
|
|
977
1058
|
#createPreviewModal(items, startIndex) {
|
|
978
1059
|
|
|
979
|
-
this.#closePreviewModal()
|
|
1060
|
+
this.#closePreviewModal({ animate: false })
|
|
1061
|
+
this.#setPreviewItems(items, startIndex)
|
|
1062
|
+
|
|
1063
|
+
const modal = this.#buildPreviewModal()
|
|
980
1064
|
|
|
1065
|
+
this.#mountPreviewModal(modal)
|
|
1066
|
+
this.#bindPreviewModalControls(modal)
|
|
1067
|
+
this.#bindPreviewKeyboardNavigation()
|
|
1068
|
+
this.#activatePreviewModal(modal)
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
#setPreviewItems(items, startIndex) {
|
|
981
1072
|
this.#previewState.items = items
|
|
982
1073
|
this.#previewState.currentIndex = startIndex
|
|
1074
|
+
}
|
|
983
1075
|
|
|
984
|
-
|
|
1076
|
+
#buildPreviewModal() {
|
|
1077
|
+
const { items, currentIndex } = this.#previewState
|
|
985
1078
|
const modal = document.createElement("div")
|
|
986
1079
|
modal.className = "hakumi-upload-preview-root"
|
|
987
1080
|
modal.innerHTML = `
|
|
@@ -991,19 +1084,19 @@ export default class extends RegistryController {
|
|
|
991
1084
|
<img class="hakumi-upload-preview-img" src="" alt="" />
|
|
992
1085
|
</div>
|
|
993
1086
|
<div class="hakumi-upload-preview-operations">
|
|
994
|
-
<button type="button" class="hakumi-upload-preview-close" title="
|
|
1087
|
+
<button type="button" class="hakumi-upload-preview-close" title="${this.closeLabelValue}">
|
|
995
1088
|
<svg viewBox="64 64 896 896" width="1em" height="1em" fill="currentColor"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"/></svg>
|
|
996
1089
|
</button>
|
|
997
1090
|
</div>
|
|
998
1091
|
${items.length > 1 ? `
|
|
999
|
-
<button type="button" class="hakumi-upload-preview-switch hakumi-upload-preview-switch-left" title="
|
|
1092
|
+
<button type="button" class="hakumi-upload-preview-switch hakumi-upload-preview-switch-left" title="${this.previousLabelValue}">
|
|
1000
1093
|
<svg viewBox="64 64 896 896" width="1em" height="1em" fill="currentColor"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"/></svg>
|
|
1001
1094
|
</button>
|
|
1002
|
-
<button type="button" class="hakumi-upload-preview-switch hakumi-upload-preview-switch-right" title="
|
|
1095
|
+
<button type="button" class="hakumi-upload-preview-switch hakumi-upload-preview-switch-right" title="${this.nextLabelValue}">
|
|
1003
1096
|
<svg viewBox="64 64 896 896" width="1em" height="1em" fill="currentColor"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V183c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"/></svg>
|
|
1004
1097
|
</button>
|
|
1005
1098
|
<div class="hakumi-upload-preview-counter">
|
|
1006
|
-
<span class="hakumi-upload-preview-counter-current">${
|
|
1099
|
+
<span class="hakumi-upload-preview-counter-current">${currentIndex + 1}</span>
|
|
1007
1100
|
<span>/</span>
|
|
1008
1101
|
<span class="hakumi-upload-preview-counter-total">${items.length}</span>
|
|
1009
1102
|
</div>
|
|
@@ -1011,28 +1104,33 @@ export default class extends RegistryController {
|
|
|
1011
1104
|
</div>
|
|
1012
1105
|
`
|
|
1013
1106
|
|
|
1107
|
+
return modal
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
#mountPreviewModal(modal) {
|
|
1014
1111
|
this.#previewState.modal = modal
|
|
1015
1112
|
document.body.appendChild(modal)
|
|
1016
1113
|
document.body.style.overflow = "hidden"
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
1114
|
this.#updatePreviewImage()
|
|
1115
|
+
}
|
|
1020
1116
|
|
|
1021
|
-
|
|
1117
|
+
#bindPreviewModalControls(modal) {
|
|
1022
1118
|
modal.querySelector(".hakumi-upload-preview-mask").addEventListener("click", () => this.#closePreviewModal())
|
|
1023
1119
|
modal.querySelector(".hakumi-upload-preview-close").addEventListener("click", () => this.#closePreviewModal())
|
|
1024
1120
|
modal.querySelector(".hakumi-upload-preview-switch-left")?.addEventListener("click", () => this.#prevPreviewImage())
|
|
1025
1121
|
modal.querySelector(".hakumi-upload-preview-switch-right")?.addEventListener("click", () => this.#nextPreviewImage())
|
|
1122
|
+
}
|
|
1026
1123
|
|
|
1027
|
-
|
|
1124
|
+
#bindPreviewKeyboardNavigation() {
|
|
1028
1125
|
this._previewKeyHandler = (e) => {
|
|
1029
1126
|
if (e.key === "Escape") this.#closePreviewModal()
|
|
1030
1127
|
else if (e.key === "ArrowLeft") this.#prevPreviewImage()
|
|
1031
1128
|
else if (e.key === "ArrowRight") this.#nextPreviewImage()
|
|
1032
1129
|
}
|
|
1033
1130
|
document.addEventListener("keydown", this._previewKeyHandler)
|
|
1131
|
+
}
|
|
1034
1132
|
|
|
1035
|
-
|
|
1133
|
+
#activatePreviewModal(modal) {
|
|
1036
1134
|
requestAnimationFrame(() => {
|
|
1037
1135
|
modal.classList.add("hakumi-upload-preview-active")
|
|
1038
1136
|
})
|
|
@@ -1067,21 +1165,39 @@ export default class extends RegistryController {
|
|
|
1067
1165
|
this.#updatePreviewImage()
|
|
1068
1166
|
}
|
|
1069
1167
|
|
|
1070
|
-
#closePreviewModal() {
|
|
1168
|
+
#closePreviewModal({ animate = true } = {}) {
|
|
1071
1169
|
const { modal } = this.#previewState
|
|
1072
1170
|
if (!modal) return
|
|
1073
1171
|
|
|
1074
1172
|
modal.classList.remove("hakumi-upload-preview-active")
|
|
1173
|
+
this.#unbindPreviewKeyboardNavigation()
|
|
1075
1174
|
|
|
1076
|
-
if (this.
|
|
1077
|
-
document.removeEventListener("keydown", this._previewKeyHandler)
|
|
1078
|
-
this._previewKeyHandler = null
|
|
1079
|
-
}
|
|
1175
|
+
if (this.#previewState.closeTimer) clearTimeout(this.#previewState.closeTimer)
|
|
1080
1176
|
|
|
1081
|
-
|
|
1177
|
+
const removeModal = () => {
|
|
1082
1178
|
modal.remove()
|
|
1083
|
-
this.#previewState.modal
|
|
1084
|
-
|
|
1085
|
-
|
|
1179
|
+
if (this.#previewState.modal === modal) {
|
|
1180
|
+
this.#resetPreviewModalState()
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (animate) {
|
|
1185
|
+
this.#previewState.closeTimer = setTimeout(removeModal, 300)
|
|
1186
|
+
} else {
|
|
1187
|
+
removeModal()
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
#unbindPreviewKeyboardNavigation() {
|
|
1192
|
+
if (!this._previewKeyHandler) return
|
|
1193
|
+
|
|
1194
|
+
document.removeEventListener("keydown", this._previewKeyHandler)
|
|
1195
|
+
this._previewKeyHandler = null
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
#resetPreviewModalState() {
|
|
1199
|
+
this.#previewState.modal = null
|
|
1200
|
+
this.#previewState.closeTimer = null
|
|
1201
|
+
document.body.style.overflow = ""
|
|
1086
1202
|
}
|
|
1087
1203
|
}
|