Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/images/apple-touch-icon.pngbin0 -> 7455 bytes
-rw-r--r--app/assets/images/ext_snippet_icons/logo.svg13
-rw-r--r--app/assets/images/favicon-blue.pngbin1522 -> 390 bytes
-rw-r--r--app/assets/images/favicon-yellow.pngbin1481 -> 373 bytes
-rw-r--r--app/assets/images/favicon.pngbin1611 -> 591 bytes
-rw-r--r--app/assets/images/file_icons.svg2
-rw-r--r--app/assets/images/gitlab_logo.pngbin3616 -> 1528 bytes
-rw-r--r--app/assets/images/logo.svg34
-rw-r--r--app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gifbin3654 -> 0 bytes
-rw-r--r--app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gifbin3040 -> 0 bytes
-rw-r--r--app/assets/images/mailers/gitlab_footer_logo.gifbin3654 -> 0 bytes
-rw-r--r--app/assets/images/mailers/gitlab_header_logo.gifbin3040 -> 0 bytes
-rw-r--r--app/assets/images/mailers/gitlab_header_logo.pngbin7096 -> 0 bytes
-rw-r--r--app/assets/images/mailers/gitlab_logo.pngbin0 -> 1528 bytes
-rw-r--r--app/assets/images/mailers/gitlab_logo_black_text.pngbin0 -> 1961 bytes
-rw-r--r--app/assets/images/mailers/gitlab_tanuki_2x.pngbin2545 -> 0 bytes
-rw-r--r--app/assets/images/mailers/in_product_marketing/gitlab-logo-gray-rgb.pngbin42439 -> 0 bytes
-rw-r--r--app/assets/images/msapplication-tile.pngbin4328 -> 0 bytes
-rw-r--r--app/assets/images/touch-icon-ipad-retina.pngbin5662 -> 0 bytes
-rw-r--r--app/assets/images/touch-icon-ipad.pngbin2465 -> 0 bytes
-rw-r--r--app/assets/images/touch-icon-iphone-retina.pngbin3460 -> 0 bytes
-rw-r--r--app/assets/images/touch-icon-iphone.pngbin1949 -> 0 bytes
-rw-r--r--app/assets/images/twitter_card.jpgbin0 -> 133437 bytes
-rw-r--r--app/assets/javascripts/access_tokens/components/projects_token_selector.vue2
-rw-r--r--app/assets/javascripts/admin/background_migrations/components/database_listbox.vue51
-rw-r--r--app/assets/javascripts/admin/background_migrations/index.js38
-rw-r--r--app/assets/javascripts/admin/statistics_panel/components/app.vue2
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_table.vue18
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue2
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql2
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql2
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql8
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql6
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/queries/get_http_integration.query.graphql2
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue33
-rw-r--r--app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue72
-rw-r--r--app/assets/javascripts/analytics/shared/constants.js36
-rw-r--r--app/assets/javascripts/analytics/usage_trends/components/users_chart.vue6
-rw-r--r--app/assets/javascripts/api.js25
-rw-r--r--app/assets/javascripts/api/integrations_api.js21
-rw-r--r--app/assets/javascripts/api/tags_api.js12
-rw-r--r--app/assets/javascripts/api/user_api.js18
-rw-r--r--app/assets/javascripts/behaviors/copy_to_clipboard.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js15
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js2
-rw-r--r--app/assets/javascripts/blob/components/blob_content.vue2
-rw-r--r--app/assets/javascripts/blob/components/blob_header.vue2
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js43
-rw-r--r--app/assets/javascripts/blob/openapi/index.js1
-rw-r--r--app/assets/javascripts/blob/pipeline_tour_success_modal.vue2
-rw-r--r--app/assets/javascripts/blob/sketch/index.js2
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js29
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column_form.vue26
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue21
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue5
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue15
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue27
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue8
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue4
-rw-r--r--app/assets/javascripts/boards/components/config_toggle.vue4
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue7
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue2
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_destroy.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql4
-rw-r--r--app/assets/javascripts/boards/graphql/board_lists.query.graphql6
-rw-r--r--app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/group_board.query.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/lists_issues.query.graphql4
-rw-r--r--app/assets/javascripts/boards/graphql/project_board.query.graphql2
-rw-r--r--app/assets/javascripts/boards/index.js1
-rw-r--r--app/assets/javascripts/boards/stores/actions.js5
-rw-r--r--app/assets/javascripts/boards/stores/getters.js14
-rw-r--r--app/assets/javascripts/branches/components/delete_branch_modal.vue37
-rw-r--r--app/assets/javascripts/captcha/captcha_modal.vue4
-rw-r--r--app/assets/javascripts/ci_secure_files/components/secure_files_list.vue202
-rw-r--r--app/assets/javascripts/ci_secure_files/index.js5
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue23
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue5
-rw-r--r--app/assets/javascripts/ci_variable_list/constants.js2
-rw-r--r--app/assets/javascripts/ci_variable_list/index.js2
-rw-r--r--app/assets/javascripts/clone_panel.js7
-rw-r--r--app/assets/javascripts/clusters/agents/components/activity_events_list.vue16
-rw-r--r--app/assets/javascripts/clusters/agents/components/revoke_token_button.vue201
-rw-r--r--app/assets/javascripts/clusters/agents/components/show.vue2
-rw-r--r--app/assets/javascripts/clusters/agents/components/token_table.vue11
-rw-r--r--app/assets/javascripts/clusters/agents/constants.js2
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/cache_update.js22
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/mutations/revoke_token.mutation.graphql5
-rw-r--r--app/assets/javascripts/clusters/components/new_cluster.vue8
-rw-r--r--app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue2
-rw-r--r--app/assets/javascripts/clusters/forms/components/integration_form.vue2
-rw-r--r--app/assets/javascripts/clusters/gke_cluster_namespace/index.js (renamed from app/assets/javascripts/create_cluster/gke_cluster_namespace/index.js)0
-rw-r--r--app/assets/javascripts/clusters/new_cluster.js8
-rw-r--r--app/assets/javascripts/clusters/stores/new_cluster/index.js12
-rw-r--r--app/assets/javascripts/clusters/stores/new_cluster/state.js3
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue17
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue2
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue4
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_actions.vue79
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_view_all.vue2
-rw-r--r--app/assets/javascripts/clusters_list/constants.js4
-rw-r--r--app/assets/javascripts/clusters_list/index.js4
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue1
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/project_form_group.vue6
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue (renamed from app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue)94
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue (renamed from app/assets/javascripts/content_editor/components/formatting_bubble_menu.vue)50
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/link.vue189
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/media.vue288
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue10
-rw-r--r--app/assets/javascripts/content_editor/components/divider.vue3
-rw-r--r--app/assets/javascripts/content_editor/components/loading_indicator.vue2
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_button.vue9
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue7
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/code_block.vue (renamed from app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue)14
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/media.vue51
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue4
-rw-r--r--app/assets/javascripts/content_editor/constants/code_block_languages.js210
-rw-r--r--app/assets/javascripts/content_editor/constants/index.js (renamed from app/assets/javascripts/content_editor/constants.js)0
-rw-r--r--app/assets/javascripts/content_editor/extensions/blockquote.js2
-rw-r--r--app/assets/javascripts/content_editor/extensions/code_block_highlight.js17
-rw-r--r--app/assets/javascripts/content_editor/extensions/diagram.js3
-rw-r--r--app/assets/javascripts/content_editor/extensions/frontmatter.js15
-rw-r--r--app/assets/javascripts/content_editor/extensions/image.js5
-rw-r--r--app/assets/javascripts/content_editor/extensions/paste_markdown.js11
-rw-r--r--app/assets/javascripts/content_editor/extensions/playable.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/sourcemap.js48
-rw-r--r--app/assets/javascripts/content_editor/services/asset_resolver.js13
-rw-r--r--app/assets/javascripts/content_editor/services/code_block_language_loader.js246
-rw-r--r--app/assets/javascripts/content_editor/services/content_editor.js46
-rw-r--r--app/assets/javascripts/content_editor/services/create_content_editor.js24
-rw-r--r--app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js (renamed from app/assets/javascripts/content_editor/services/markdown_deserializer.js)2
-rw-r--r--app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js475
-rw-r--r--app/assets/javascripts/content_editor/services/highlight_js_language_loader.js248
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js115
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_sourcemap.js2
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js87
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js131
-rw-r--r--app/assets/javascripts/content_editor/services/upload_helpers.js4
-rw-r--r--app/assets/javascripts/content_editor/services/utils.js2
-rw-r--r--app/assets/javascripts/create_cluster/components/cluster_form_dropdown.vue246
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue58
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue530
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue182
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/constants.js9
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/index.js55
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js79
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/actions.js148
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/getters.js2
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/index.js49
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js19
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js66
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/state.js34
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js70
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue112
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue53
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue194
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_submit_button.vue18
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue44
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue101
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/constants.js11
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/gapi_loader.js24
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/index.js95
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/actions.js99
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/getters.js5
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/index.js18
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js8
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js28
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/state.js13
-rw-r--r--app/assets/javascripts/create_cluster/init_create_cluster.js35
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/actions.js14
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/getters.js0
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/index.js13
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/mutation_types.js3
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/mutations.js16
-rw-r--r--app/assets/javascripts/create_cluster/store/cluster_dropdown/state.js5
-rw-r--r--app/assets/javascripts/crm/components/form.vue39
-rw-r--r--app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue58
-rw-r--r--app/assets/javascripts/crm/contacts/components/contacts_root.vue2
-rw-r--r--app/assets/javascripts/crm/organizations/components/organizations_root.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/base.vue21
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_table.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue57
-rw-r--r--app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue4
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql2
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql1
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql1
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue4
-rw-r--r--app/assets/javascripts/diffs/components/app.vue13
-rw-r--r--app/assets/javascripts/diffs/components/compare_dropdown_layout.vue7
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_expansion_cell.vue158
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue106
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue7
-rw-r--r--app/assets/javascripts/diffs/components/diff_row.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_row_utils.js7
-rw-r--r--app/assets/javascripts/diffs/components/diff_view.vue85
-rw-r--r--app/assets/javascripts/diffs/components/hidden_files_warning.vue2
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue4
-rw-r--r--app/assets/javascripts/diffs/constants.js3
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js2
-rw-r--r--app/assets/javascripts/diffs/store/utils.js10
-rw-r--r--app/assets/javascripts/diffs/utils/diff_file.js27
-rw-r--r--app/assets/javascripts/diffs/utils/queue_events.js26
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar.vue6
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar_button.vue74
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js53
-rw-r--r--app/assets/javascripts/editor/constants.js5
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_extension_base.js4
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js108
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js98
-rw-r--r--app/assets/javascripts/editor/graphql/add_items.mutation.graphql3
-rw-r--r--app/assets/javascripts/editor/graphql/get_item.query.graphql9
-rw-r--r--app/assets/javascripts/editor/graphql/remove_items.mutation.graphql3
-rw-r--r--app/assets/javascripts/editor/graphql/typedefs.graphql23
-rw-r--r--app/assets/javascripts/editor/graphql/update_item.mutation.graphql2
-rw-r--r--app/assets/javascripts/editor/schema/ci.json21
-rw-r--r--app/assets/javascripts/environments/components/enable_review_app_modal.vue13
-rw-r--r--app/assets/javascripts/environments/components/environment_folder.vue8
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue2
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue4
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue2
-rw-r--r--app/assets/javascripts/error_tracking/queries/details.query.graphql2
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/app.vue2
-rw-r--r--app/assets/javascripts/filtered_search/available_dropdown_mappings.js27
-rw-r--r--app/assets/javascripts/filtered_search/constants.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js4
-rw-r--r--app/assets/javascripts/filtered_search/group_runners_filtered_search_token_keys.js27
-rw-r--r--app/assets/javascripts/filtered_search/null_dropdown.js9
-rw-r--r--app/assets/javascripts/flash.js86
-rw-r--r--app/assets/javascripts/frequent_items/constants.js2
-rw-r--r--app/assets/javascripts/frequent_items/utils.js5
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js2
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql1
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/user_availability.fragment.graphql1
-rw-r--r--app/assets/javascripts/graphql_shared/mutations/todo_mark_done.mutation.graphql2
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json4
-rw-r--r--app/assets/javascripts/group.js37
-rw-r--r--app/assets/javascripts/group_settings/components/shared_runners_form.vue128
-rw-r--r--app/assets/javascripts/group_settings/constants.js3
-rw-r--r--app/assets/javascripts/group_settings/mount_shared_runners.js20
-rw-r--r--app/assets/javascripts/header_search/components/app.vue4
-rw-r--r--app/assets/javascripts/header_search/index.js16
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue4
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue2
-rw-r--r--app/assets/javascripts/import_entities/components/import_status.vue5
-rw-r--r--app/assets/javascripts/import_entities/constants.js1
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table.vue23
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue1
-rw-r--r--app/assets/javascripts/import_entities/import_groups/constants.js2
-rw-r--r--app/assets/javascripts/import_entities/import_groups/index.js2
-rw-r--r--app/assets/javascripts/import_entities/import_groups/utils.js2
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue16
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue9
-rw-r--r--app/assets/javascripts/incidents/graphql/fragments/incident_fields.fragment.graphql1
-rw-r--r--app/assets/javascripts/integrations/constants.js10
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue26
-rw-r--r--app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue104
-rw-r--r--app/assets/javascripts/integrations/edit/index.js2
-rw-r--r--app/assets/javascripts/invite_members/components/import_a_project_modal.vue2
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue26
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue171
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue4
-rw-r--r--app/assets/javascripts/invite_members/components/user_limit_notification.vue75
-rw-r--r--app/assets/javascripts/invite_members/constants.js32
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js9
-rw-r--r--app/assets/javascripts/issuable/components/issuable_header_warnings.vue27
-rw-r--r--app/assets/javascripts/issuable/components/related_issuable_item.vue2
-rw-r--r--app/assets/javascripts/issuable/components/status_box.vue91
-rw-r--r--app/assets/javascripts/issuable/issuable_form.js3
-rw-r--r--app/assets/javascripts/issues/create_merge_request_dropdown.js9
-rw-r--r--app/assets/javascripts/issues/issue.js4
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue109
-rw-r--r--app/assets/javascripts/issues/list/constants.js29
-rw-r--r--app/assets/javascripts/issues/list/index.js18
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues.query.graphql7
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql14
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql136
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql94
-rw-r--r--app/assets/javascripts/issues/list/queries/issue.fragment.graphql4
-rw-r--r--app/assets/javascripts/issues/list/utils.js25
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue90
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue156
-rw-r--r--app/assets/javascripts/issues/show/index.js2
-rw-r--r--app/assets/javascripts/issues/show/utils.js99
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/api.js10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue52
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue48
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue21
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/constants.js17
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/index.js7
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue65
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue68
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue72
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue88
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue35
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue43
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue54
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/actions.js73
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/index.js11
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js12
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutations.js40
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/state.js20
-rw-r--r--app/assets/javascripts/jira_import/components/jira_import_form.vue5
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue4
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue1
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_job_details_container.vue27
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue4
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue13
-rw-r--r--app/assets/javascripts/jobs/components/table/graphql/fragments/job.fragment.graphql4
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue4
-rw-r--r--app/assets/javascripts/jobs/store/getters.js12
-rw-r--r--app/assets/javascripts/lib/dompurify.js1
-rw-r--r--app/assets/javascripts/lib/gfm/index.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js58
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue20
-rw-r--r--app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js4
-rw-r--r--app/assets/javascripts/lib/utils/cookies.js8
-rw-r--r--app/assets/javascripts/lib/utils/datetime/timeago_utility.js2
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js14
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js1
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js17
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js4
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js13
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js11
-rw-r--r--app/assets/javascripts/main.js10
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue2
-rw-r--r--app/assets/javascripts/merge_request.js31
-rw-r--r--app/assets/javascripts/merge_request_tabs.js47
-rw-r--r--app/assets/javascripts/milestones/components/milestone_combobox.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue5
-rw-r--r--app/assets/javascripts/mr_notes/index.js8
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue22
-rw-r--r--app/assets/javascripts/notes/components/comment_type_dropdown.vue48
-rw-r--r--app/assets/javascripts/notes/components/diff_discussion_header.vue5
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue113
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue5
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue25
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue20
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue20
-rw-r--r--app/assets/javascripts/notes/i18n.js14
-rw-r--r--app/assets/javascripts/notes/stores/actions.js17
-rw-r--r--app/assets/javascripts/notes/stores/getters.js33
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue64
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js8
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue90
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue24
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/index.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue5
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue82
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue40
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/bundle.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue15
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue3
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/constants.js5
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue76
-rw-r--r--app/assets/javascripts/pages/admin/background_migrations/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/clusters/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/clusters/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/new/components/app.vue4
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js15
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue16
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue41
-rw-r--r--app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js28
-rw-r--r--app/assets/javascripts/pages/projects/project.js20
-rw-r--r--app/assets/javascripts/pages/projects/serverless/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/services/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js55
-rw-r--r--app/assets/javascripts/pages/projects/wikis/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/wikis/show/index.js2
-rw-r--r--app/assets/javascripts/pages/registrations/new/index.js5
-rw-r--r--app/assets/javascripts/persistent_user_callouts.js1
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue26
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue13
-rw-r--r--app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue4
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue48
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_tree/container.vue78
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_tree/file_item.vue45
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue22
-rw-r--r--app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue59
-rw-r--r--app/assets/javascripts/pipeline_editor/components/popovers/walkthrough_popover.vue (renamed from app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue)2
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js4
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql6
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js4
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue3
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue67
-rw-r--r--app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue2
-rw-r--r--app/assets/javascripts/pipeline_new/index.js2
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/constants.js4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue60
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue22
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue151
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue17
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue111
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/utils.js33
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_tabs.vue40
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue24
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue37
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue220
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue29
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue16
-rw-r--r--app/assets/javascripts/pipelines/constants.js47
-rw-r--r--app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql12
-rw-r--r--app/assets/javascripts/pipelines/graphql/provider.js9
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql41
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js14
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_dag.js2
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js36
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_header.js2
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_notification.js2
-rw-r--r--app/assets/javascripts/pipelines/pipeline_tabs.js27
-rw-r--r--app/assets/javascripts/pipelines/pipelines_index.js8
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/actions.js6
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/getters.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js1
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutations.js14
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/state.js1
-rw-r--r--app/assets/javascripts/pipelines/utils.js19
-rw-r--r--app/assets/javascripts/project_select_combo_button.js3
-rw-r--r--app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue14
-rw-r--r--app/assets/javascripts/projects/project_new.js5
-rw-r--r--app/assets/javascripts/projects/settings/init_access_dropdown.js2
-rw-r--r--app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue2
-rw-r--r--app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql1
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue24
-rw-r--r--app/assets/javascripts/prometheus_alerts/components/reset_key.vue149
-rw-r--r--app/assets/javascripts/prometheus_alerts/index.js28
-rw-r--r--app/assets/javascripts/related_issues/components/add_issuable_form.vue5
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_root.vue3
-rw-r--r--app/assets/javascripts/releases/components/app_edit_new.vue38
-rw-r--r--app/assets/javascripts/releases/components/tag_field_new.vue15
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/actions.js22
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/getters.js22
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js6
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/mutations.js18
-rw-r--r--app/assets/javascripts/releases/stores/modules/edit_new/state.js6
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/getters.js14
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue19
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue5
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue29
-rw-r--r--app/assets/javascripts/repository/constants.js21
-rw-r--r--app/assets/javascripts/repository/index.js8
-rw-r--r--app/assets/javascripts/rest_api.js1
-rw-r--r--app/assets/javascripts/right_sidebar.js27
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue21
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/index.js3
-rw-r--r--app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue20
-rw-r--r--app/assets/javascripts/runner/admin_runner_show/index.js6
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue104
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js3
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_dropdown.vue2
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue8
-rw-r--r--app/assets/javascripts/runner/components/runner_delete_button.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_details.vue3
-rw-r--r--app/assets/javascripts/runner/components/runner_jobs.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_pagination.vue4
-rw-r--r--app/assets/javascripts/runner/components/runner_projects.vue2
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue210
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner.query.graphql9
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql5
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql35
-rw-r--r--app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql15
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql5
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql15
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql7
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql13
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner.query.graphql41
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql (renamed from app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql)0
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql (renamed from app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql)0
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue111
-rw-r--r--app/assets/javascripts/runner/local_storage_alert/constants.js1
-rw-r--r--app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js8
-rw-r--r--app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js18
-rw-r--r--app/assets/javascripts/runner/runner_search_utils.js6
-rw-r--r--app/assets/javascripts/runner/utils.js3
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue30
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js8
-rw-r--r--app/assets/javascripts/security_configuration/components/feature_card.vue6
-rw-r--r--app/assets/javascripts/security_configuration/components/section_layout.vue23
-rw-r--r--app/assets/javascripts/security_configuration/graphql/current_license.query.graphql6
-rw-r--r--app/assets/javascripts/security_configuration/graphql/security_training_vulnerability.query.graphql1
-rw-r--r--app/assets/javascripts/security_configuration/index.js1
-rw-r--r--app/assets/javascripts/serverless/components/area.vue145
-rw-r--r--app/assets/javascripts/serverless/components/empty_state.vue39
-rw-r--r--app/assets/javascripts/serverless/components/environment_row.vue65
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue94
-rw-r--r--app/assets/javascripts/serverless/components/function_row.vue77
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue139
-rw-r--r--app/assets/javascripts/serverless/components/missing_prometheus.vue57
-rw-r--r--app/assets/javascripts/serverless/components/pod_box.vue36
-rw-r--r--app/assets/javascripts/serverless/components/url.vue28
-rw-r--r--app/assets/javascripts/serverless/constants.js10
-rw-r--r--app/assets/javascripts/serverless/event_hub.js3
-rw-r--r--app/assets/javascripts/serverless/serverless_bundle.js67
-rw-r--r--app/assets/javascripts/serverless/store/actions.js131
-rw-r--r--app/assets/javascripts/serverless/store/getters.js7
-rw-r--r--app/assets/javascripts/serverless/store/index.js18
-rw-r--r--app/assets/javascripts/serverless/store/mutation_types.js11
-rw-r--r--app/assets/javascripts/serverless/store/mutations.js49
-rw-r--r--app/assets/javascripts/serverless/store/state.js22
-rw-r--r--app/assets/javascripts/serverless/utils.js20
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue20
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue32
-rw-r--r--app/assets/javascripts/sidebar/components/attention_requested_toggle.vue33
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue31
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue16
-rw-r--r--app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue55
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue15
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_editable_item.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue18
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/report.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue8
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js10
-rw-r--r--app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql7
-rw-r--r--app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql5
-rw-r--r--app/assets/javascripts/sidebar/queries/reviewer_rereview.mutation.graphql2
-rw-r--r--app/assets/javascripts/sidebar/queries/toggle_attention_requested.mutation.graphql7
-rw-r--r--app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql2
-rw-r--r--app/assets/javascripts/sidebar/services/sidebar_service.js17
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js33
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js35
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue57
-rw-r--r--app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue38
-rw-r--r--app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql2
-rw-r--r--app/assets/javascripts/sortable/constants.js4
-rw-r--r--app/assets/javascripts/sortable/utils.js10
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue4
-rw-r--r--app/assets/javascripts/tracking/dispatch_snowplow_event.js9
-rw-r--r--app/assets/javascripts/tracking/tracker.js267
-rw-r--r--app/assets/javascripts/tracking/tracking.js275
-rw-r--r--app/assets/javascripts/user_popovers.js16
-rw-r--r--app/assets/javascripts/users_select/index.js18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue43
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue77
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue27
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue57
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js84
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js45
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue25
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js6
-rw-r--r--app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue33
-rw-r--r--app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/confidentiality_badge.vue39
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/deployment_instance.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue49
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue52
-rw-r--r--app/assets/javascripts/vue_shared/components/file_finder/index.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue54
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue15
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue26
-rw-r--r--app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/paginated_list.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/list_item.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/registry_search.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue68
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue90
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue2
-rw-r--r--app/assets/javascripts/vue_shared/constants.js14
-rw-r--r--app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue19
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue15
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue6
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue42
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue8
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue2
-rw-r--r--app/assets/javascripts/vue_shared/mixins/timeago.js4
-rw-r--r--app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue34
-rw-r--r--app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue35
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/store/utils.js2
-rw-r--r--app/assets/javascripts/whats_new/components/feature.vue8
-rw-r--r--app/assets/javascripts/work_items/components/item_state.vue62
-rw-r--r--app/assets/javascripts/work_items/components/work_item_actions.vue48
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue53
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail_modal.vue103
-rw-r--r--app/assets/javascripts/work_items/components/work_item_state.vue98
-rw-r--r--app/assets/javascripts/work_items/components/work_item_title.vue11
-rw-r--r--app/assets/javascripts/work_items/constants.js8
-rw-r--r--app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql9
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.fragment.graphql6
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.query.graphql2
-rw-r--r--app/assets/javascripts/work_items/index.js3
-rw-r--r--app/assets/javascripts/work_items/pages/create_work_item.vue23
-rw-r--r--app/assets/javascripts/work_items/pages/work_item_root.vue46
-rw-r--r--app/assets/javascripts/work_items/router/index.js2
-rw-r--r--app/assets/javascripts/work_items_hierarchy/components/app.vue2
-rw-r--r--app/assets/stylesheets/bootstrap_migration_components.scss11
-rw-r--r--app/assets/stylesheets/components/content_editor.scss9
-rw-r--r--app/assets/stylesheets/components/dashboard_skeleton.scss3
-rw-r--r--app/assets/stylesheets/components/design_management/design.scss2
-rw-r--r--app/assets/stylesheets/components/whats_new.scss6
-rw-r--r--app/assets/stylesheets/errors.scss8
-rw-r--r--app/assets/stylesheets/framework/broadcast_messages.scss7
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/framework/diffs.scss40
-rw-r--r--app/assets/stylesheets/framework/files.scss13
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss46
-rw-r--r--app/assets/stylesheets/framework/icons.scss72
-rw-r--r--app/assets/stylesheets/framework/logo.scss101
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss6
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss60
-rw-r--r--app/assets/stylesheets/framework/tables.scss24
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/framework/wells.scss2
-rw-r--r--app/assets/stylesheets/highlight/common.scss22
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss12
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss13
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss9
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss13
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss13
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss28
-rw-r--r--app/assets/stylesheets/mailer.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/_pipeline_mixins.scss92
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss37
-rw-r--r--app/assets/stylesheets/page_bundles/dashboard_projects.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/issues_show.scss67
-rw-r--r--app/assets/stylesheets/page_bundles/jira_connect.scss11
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss62
-rw-r--r--app/assets/stylesheets/page_bundles/milestone.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/pipeline.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/pipeline_editor.scss22
-rw-r--r--app/assets/stylesheets/page_bundles/pipelines.scss65
-rw-r--r--app/assets/stylesheets/page_bundles/terms.scss8
-rw-r--r--app/assets/stylesheets/pages/commits.scss5
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss23
-rw-r--r--app/assets/stylesheets/pages/issuable.scss107
-rw-r--r--app/assets/stylesheets/pages/issues.scss29
-rw-r--r--app/assets/stylesheets/pages/login.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss29
-rw-r--r--app/assets/stylesheets/pages/projects.scss25
-rw-r--r--app/assets/stylesheets/pages/search.scss46
-rw-r--r--app/assets/stylesheets/pages/tree.scss6
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss112
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss105
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss34
-rw-r--r--app/assets/stylesheets/themes/_dark.scss2
-rw-r--r--app/assets/stylesheets/themes/dark_mode_overrides.scss7
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss1
-rw-r--r--app/assets/stylesheets/themes/theme_light.scss5
-rw-r--r--app/assets/stylesheets/utilities.scss27
744 files changed, 11394 insertions, 8183 deletions
diff --git a/app/assets/images/apple-touch-icon.png b/app/assets/images/apple-touch-icon.png
new file mode 100644
index 00000000000..90507f61099
--- /dev/null
+++ b/app/assets/images/apple-touch-icon.png
Binary files differ
diff --git a/app/assets/images/ext_snippet_icons/logo.svg b/app/assets/images/ext_snippet_icons/logo.svg
index 9cb3042213a..d76e9cb3e5c 100644
--- a/app/assets/images/ext_snippet_icons/logo.svg
+++ b/app/assets/images/ext_snippet_icons/logo.svg
@@ -1 +1,12 @@
-<svg width="100" height="32" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path fill="#8C929D" d="M67.67 8.11h-2.06l.009 15.364h8.348v-1.9H67.68l-.01-13.465zM81.913 20.778a3.517 3.517 0 01-2.553 1.078c-1.57 0-2.203-.775-2.203-1.787 0-1.522 1.059-2.25 3.309-2.25.487.002.974.04 1.456.113v2.846h-.01zm-2.137-9.313a6.826 6.826 0 00-4.387 1.579l.728 1.267c.841-.492 1.872-.983 3.356-.983 1.693 0 2.44.87 2.44 2.326v.747a9.4 9.4 0 00-1.428-.114c-3.612 0-5.446 1.267-5.446 3.914 0 2.374 1.456 3.565 3.659 3.565 1.484 0 2.912-.68 3.404-1.787l.378 1.503h1.456v-7.866c-.01-2.487-1.087-4.151-4.16-4.151zM90.587 21.926c-.776 0-1.456-.094-1.967-.33v-7.102c.7-.586 1.57-1.011 2.676-1.011 1.995 0 2.76 1.408 2.76 3.687 0 3.234-1.238 4.756-3.47 4.756m.87-10.457a3.775 3.775 0 00-2.836 1.257V10.74l-.01-2.629h-2.013l.01 14.987c1.01.425 2.391.652 3.895.652 3.848 0 5.701-2.458 5.701-6.704-.01-3.356-1.72-5.578-4.746-5.578M45.228 9.776c1.825 0 3.006.605 3.772 1.22l.889-1.541c-1.2-1.06-2.827-1.627-4.567-1.627-4.387 0-7.46 2.676-7.46 8.075 0 5.654 3.319 7.857 7.11 7.857a12.083 12.083 0 004.577-.888L49.5 16.83v-1.9h-5.63v1.9h3.594l.047 4.586c-.473.236-1.286.425-2.392.425-3.045 0-5.087-1.92-5.087-5.957-.01-4.113 2.1-6.108 5.19-6.108M59.744 8.107H57.73l.01 2.582v8.916c0 2.487 1.078 4.15 4.15 4.15.416.002.83-.036 1.24-.113v-1.806c-.31.047-.624.07-.937.066-1.692 0-2.44-.87-2.44-2.326v-6.145h3.376v-1.683h-3.373l-.009-3.64h-.003zM52.608 23.474h2.014V11.75h-2.014zM52.608 10.133h2.014V8.119h-2.014z"/><path d="M31.864 17.907l-1.788-5.496-3.538-10.9a.612.612 0 00-1.16 0L21.84 12.406H10.085L6.547 1.512a.612.612 0 00-1.16 0L1.855 12.405.066 17.907c-.162.5.015 1.05.44 1.36L15.963 30.5l15.456-11.233a1.22 1.22 0 00.446-1.36" fill="#FC6D26"/><path d="M15.966 30.49l5.875-18.086H10.09z" fill="#E24329"/><path d="M15.962 30.49l-5.877-18.086H1.859z" fill="#FC6D26"/><path d="M1.852 12.41L.063 17.906c-.162.5.015 1.05.441 1.36L15.959 30.5 1.852 12.41z" fill="#FCA326"/><path d="M1.854 12.41h8.237L6.546 1.517a.612.612 0 00-1.16 0L1.854 12.41z" fill="#E24329"/><path d="M15.966 30.49l5.875-18.086h8.236z" fill="#FC6D26"/><path d="M30.074 12.41l1.79 5.496a1.219 1.219 0 01-.44 1.36L15.966 30.49l14.107-18.08z" fill="#FCA326"/><path d="M30.079 12.41H21.84L25.38 1.517a.612.612 0 011.16 0l3.539 10.893z" fill="#E24329"/></g></svg> \ No newline at end of file
+<svg width="111" height="24" viewBox="0 0 111 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M44.814 9.042h3.645c-.608-3.875-3.963-6.574-8.33-6.574-5.166 0-9.043 3.798-9.043 10.16 0 6.248 3.703 10.123 9.15 10.123 4.887 0 8.386-3.144 8.386-8.234v-2.37h-8.01v2.794h4.55c-.058 2.816-1.938 4.599-4.908 4.599-3.305 0-5.57-2.477-5.57-6.95 0-4.445 2.303-6.913 5.494-6.913 2.38 0 4.01 1.272 4.636 3.365Zm6.218 13.438h3.49V7.68h-3.49v14.8Zm1.76-17.151c1.109 0 2.014-.85 2.014-1.89s-.905-1.9-2.014-1.9c-1.109 0-2.024.849-2.024 1.9s.9 1.89 2.017 1.89h.007ZM64.971 7.68H62.05V4.126h-3.49v3.556h-2.1v2.699h2.1v8.233c-.018 2.786 2.007 4.16 4.628 4.079a7.089 7.089 0 0 0 2.055-.348l-.59-2.73a4.247 4.247 0 0 1-1.02.137c-.878 0-1.582-.309-1.582-1.717v-7.662h2.921V7.68Zm2.701 14.8h12.272v-2.998H71.25V2.737h-3.578V22.48Zm18.957.3c2.323 0 3.71-1.09 4.347-2.333h.115v2.033h3.36v-9.91c0-3.913-3.19-5.09-6.016-5.09-3.113 0-5.504 1.388-6.275 4.087l3.26.464c.345-1.013 1.329-1.88 3.04-1.88 1.62 0 2.506.829 2.506 2.285v.057c0 1.002-1.05 1.051-3.664 1.33-2.872.309-5.619 1.166-5.619 4.502-.01 2.912 2.12 4.455 4.946 4.455Zm1.147-2.56c-1.456 0-2.498-.666-2.498-1.948 0-1.34 1.167-1.899 2.72-2.121.917-.125 2.75-.357 3.2-.722v1.744c.01 1.643-1.321 3.042-3.422 3.042v.005Zm9.244 2.26h3.433v-2.332h.201c.551 1.08 1.698 2.593 4.244 2.593 3.489 0 6.102-2.768 6.102-7.644 0-4.936-2.69-7.616-6.112-7.616-2.613 0-3.702 1.57-4.234 2.641h-.147V2.737h-3.486V22.48Zm3.423-7.403c0-2.88 1.234-4.734 3.48-4.734 2.323 0 3.52 1.976 3.52 4.734 0 2.759-1.214 4.8-3.52 4.8-2.227 0-3.48-1.928-3.48-4.8Z"
+ fill="#171321"/>
+ <path d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
+ fill="#E24329"/>
+ <path d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
+ fill="#FC6D26"/>
+ <path d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"
+ fill="#FCA326"/>
+ <path d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"
+ fill="#FC6D26"/>
+</svg>
diff --git a/app/assets/images/favicon-blue.png b/app/assets/images/favicon-blue.png
index 2229fe79462..4829a48a0ea 100644
--- a/app/assets/images/favicon-blue.png
+++ b/app/assets/images/favicon-blue.png
Binary files differ
diff --git a/app/assets/images/favicon-yellow.png b/app/assets/images/favicon-yellow.png
index a80827808fc..fa0a23d54e5 100644
--- a/app/assets/images/favicon-yellow.png
+++ b/app/assets/images/favicon-yellow.png
Binary files differ
diff --git a/app/assets/images/favicon.png b/app/assets/images/favicon.png
index 845e0ec34a5..feeff619345 100644
--- a/app/assets/images/favicon.png
+++ b/app/assets/images/favicon.png
Binary files differ
diff --git a/app/assets/images/file_icons.svg b/app/assets/images/file_icons.svg
index def87dd9163..8b19f411c7b 100644
--- a/app/assets/images/file_icons.svg
+++ b/app/assets/images/file_icons.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" id="actionscript" xmlns="http://www.w3.org/2000/svg"><text style="line-height:113.99999857%" x="5.605" y="15.892" transform="scale(.91325 1.095)" font-weight="400" font-size="42.822" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/><path style="line-height:125%" d="M4.744 2.031c-1.157 0-1.994.31-2.51.93-.515.612-.771 1.678-.771 3.197v2.467c0 1.408-.402 2.111-1.201 2.111v2.035c.8 0 1.2.679 1.2 2.036v2.654c0 1.512.26 2.562.78 3.152.52.59 1.355.885 2.502.885V19.43c-.447 0-.77-.151-.97-.453-.195-.303-.292-.815-.292-1.538v-2.267c0-1.807-.404-2.937-1.214-3.395v-.045c.81-.464 1.214-1.581 1.214-3.351V6.025c0-1.283.42-1.925 1.262-1.925V2.03zm14.66 0V4.1c.842 0 1.262.642 1.262 1.925v2.268c0 1.843.402 2.996 1.207 3.46v.046c-.805.442-1.207 1.544-1.207 3.306v2.356c0 .715-.099 1.22-.299 1.516-.2.302-.52.453-.963.453v2.068c1.152 0 1.984-.295 2.494-.885.516-.59.772-1.663.772-3.218V14.84c0-1.379.404-2.069 1.209-2.069v-2.035c-.805 0-1.21-.696-1.21-2.09V6.113c0-1.49-.255-2.54-.77-3.152-.516-.62-1.348-.93-2.495-.93zm-3.054 4.46c-.455 0-.886.057-1.293.173a3.056 3.056 0 0 0-1.078.527c-.308.241-.551.549-.731.924-.18.37-.27.817-.27 1.336 0 .663.165 1.227.493 1.695.33.468.831.864 1.502 1.188.263.125.509.249.736.37.227.12.422.244.586.374.168.13.299.271.394.424a.963.963 0 0 1 .145.521c0 .144-.03.28-.09.405a.9.9 0 0 1-.275.318c-.12.088-.272.158-.455.21a2.34 2.34 0 0 1-.635.075c-.415 0-.825-.083-1.233-.25a3.644 3.644 0 0 1-1.13-.763v2.222a3.68 3.68 0 0 0 1.101.418c.427.093.875.139 1.346.139.459 0 .894-.05 1.305-.152a3.002 3.002 0 0 0 1.09-.5c.31-.237.556-.543.736-.918.183-.38.275-.849.275-1.405 0-.403-.052-.755-.156-1.056a2.542 2.542 0 0 0-.45-.813 3.295 3.295 0 0 0-.704-.633 6.754 6.754 0 0 0-.922-.535 12.4 12.4 0 0 1-.676-.348c-.2-.115-.37-.231-.51-.347a1.502 1.502 0 0 1-.322-.375.91.91 0 0 1-.115-.453c0-.153.033-.288.101-.408a.948.948 0 0 1 .29-.32c.123-.089.275-.156.454-.202a2.18 2.18 0 0 1 .598-.078c.16 0 .326.015.502.043.18.028.36.07.539.13.18.056.354.13.522.218.171.088.329.188.472.304V6.871a4.039 4.039 0 0 0-.957-.285 6.448 6.448 0 0 0-1.185-.096zm-8.774.165l-3.123 9.967h2.094l.605-2.217h3.053l.61 2.217h2.107L9.869 6.656H7.576zm1.072 1.78h.047c.028.347.077.646.145.896l.922 3.35H7.564l.934-3.377c.08-.288.13-.578.15-.87z" font-weight="400" font-size="51.019" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="android" xmlns="http://www.w3.org/2000/svg"><path d="M15 5h-1V4h1m-5 1H9V4h1m5.53-1.84L16.84.85c.19-.19.19-.51 0-.71a.513.513 0 0 0-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.14a.501.501 0 0 0-.7 0c-.2.2-.2.52 0 .71l1.31 1.31C6.97 3.26 6 5 6 7h12c0-2-1-3.75-2.47-4.84M20.5 8A1.5 1.5 0 0 0 19 9.5v7a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 20.5 8m-17 0A1.5 1.5 0 0 0 2 9.5v7A1.5 1.5 0 0 0 3.5 18 1.5 1.5 0 0 0 5 16.5v-7A1.5 1.5 0 0 0 3.5 8M6 18a1 1 0 0 0 1 1h1v3.5A1.5 1.5 0 0 0 9.5 24a1.5 1.5 0 0 0 1.5-1.5V19h2v3.5a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5V19h1a1 1 0 0 0 1-1V8H6v10z" fill="#c0ca33"/></symbol><symbol viewBox="0 0 24 24" id="angular" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="angular-component" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#0288d1"/></symbol><symbol viewBox="0 0 24 24" id="angular-directive" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ab47bc"/></symbol><symbol viewBox="0 0 24 24" id="angular-guard" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-pipe" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#00897b"/></symbol><symbol viewBox="0 0 24 24" id="angular-resolver" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-routing" xmlns="http://www.w3.org/2000/svg"><path d="M11 10H5L3 8l2-2h6V3l1-1 1 1v1h6l2 2-2 2h-6v2h6l2 2-2 2h-6v6a2 2 0 0 1 2 2H9a2 2 0 0 1 2-2V10z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-service" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ffca28"/></symbol><symbol viewBox="0 0 100 100" id="apiblueprint" xmlns="http://www.w3.org/2000/svg"><title>api-blueprint</title><path d="M50.133 7.521A16.998 16.998 0 0 0 33.135 24.52a16.998 16.998 0 0 0 4.945 11.974L24.861 57.398a16.998 16.998 0 0 0-3.175-.308A16.998 16.998 0 0 0 4.688 74.088a16.998 16.998 0 0 0 16.998 16.998 16.998 16.998 0 0 0 16.998-16.998 16.998 16.998 0 0 0-7.063-13.773l12.576-19.89a16.998 16.998 0 0 0 5.936 1.093 16.998 16.998 0 0 0 6.154-1.155l12.537 19.83a16.998 16.998 0 0 0-7.244 13.895 16.998 16.998 0 0 0 16.998 17 16.998 16.998 0 0 0 16.998-17A16.998 16.998 0 0 0 78.578 57.09a16.998 16.998 0 0 0-2.95.262L62.337 36.327A16.998 16.998 0 0 0 67.13 24.52 16.998 16.998 0 0 0 50.132 7.522z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="applescript" xmlns="http://www.w3.org/2000/svg"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" fill="#78909c"/></symbol><symbol viewBox="0 0 24 24" id="appveyor" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-.084 0-.165.008-.248.01a10 10 0 0 0-.266.01 9.952 9.952 0 0 0-.754.066 10 10 0 0 0-.148.018 9.855 9.855 0 0 0-.93.177 10 10 0 0 0-.07.02c-.196.049-.392.1-.584.16v.012a10 10 0 0 0-2 .875V3.34c-.02.012-.038.027-.059.039a10 10 0 0 0-.953.635c-.09.067-.172.142-.26.213a10 10 0 0 0-.628.546c-.109.104-.211.211-.315.319a10 10 0 0 0-.476.539c-.1.12-.201.237-.295.361a10 10 0 0 0-.52.766c-.088.143-.17.288-.252.435a10 10 0 0 0-.363.723c-.072.161-.136.327-.2.492a10 10 0 0 0-.269.778c-.02.067-.044.131-.062.199a10 10 0 0 0-.008.027c-.098.364-.166.728-.22 1.09-.012.077-.024.153-.034.23a9.85 9.85 0 0 0-.08 1.182c0 .03-.006.057-.006.086a10 10 0 0 0 .008.148c.001.094-.002.188.002.282l.011.004a10 10 0 0 0 .333 2.158l-.012-.004c.012.047.033.091.047.139a10 10 0 0 0 .322.955c.02.052.037.106.059.158a10 10 0 0 0 .503 1.035c.065.116.14.226.21.34a10 10 0 0 0 .423.64c.092.128.187.252.285.375a10 10 0 0 0 .448.52c.112.123.222.248.341.365a10 10 0 0 0 .803.719 10 10 0 0 0 .01.006c.099.078.207.146.309.22a10 10 0 0 0 .648.442c.138.085.28.163.424.242a10 10 0 0 0 .715.358c.114.051.226.106.343.154a10 10 0 0 0 1.133.389c.016.004.031.01.047.015a10 10 0 0 0 .461.098 10 10 0 0 0 .482.103 10 10 0 0 0 .418.051 10 10 0 0 0 .575.065 10 10 0 0 0 .144.005A10 10 0 0 0 12 22a10 10 0 0 0 .197-.01 10 10 0 0 0 .496-.025 10 10 0 0 0 .49-.043 10 10 0 0 0 .489-.074 10 10 0 0 0 .51-.098 10 10 0 0 0 .47-.12 10 10 0 0 0 .477-.14 10 10 0 0 0 .47-.172 10 10 0 0 0 .481-.197 10 10 0 0 0 .414-.201 10 10 0 0 0 .475-.252 10 10 0 0 0 .39-.238 10 10 0 0 0 .452-.301 10 10 0 0 0 .38-.291 10 10 0 0 0 .385-.315 10 10 0 0 0 .375-.347 10 10 0 0 0 .36-.363 10 10 0 0 0 .293-.334 10 10 0 0 0 .353-.434 10 10 0 0 0 .28-.393 10 10 0 0 0 .263-.4 10 10 0 0 0 .264-.461 10 10 0 0 0 .228-.436 10 10 0 0 0 .195-.437 10 10 0 0 0 .196-.48 10 10 0 0 0 .228-.69 10 10 0 0 0 .028-.094 10 10 0 0 0 .021-.066 10 10 0 0 0 .098-.461 10 10 0 0 0 .103-.482 10 10 0 0 0 .051-.418 10 10 0 0 0 .065-.575 10 10 0 0 0 .005-.144A10 10 0 0 0 22 12a10 10 0 0 0-.01-.197 10 10 0 0 0-.025-.496 10 10 0 0 0-.043-.49 10 10 0 0 0-.074-.489 10 10 0 0 0-.098-.51 10 10 0 0 0-.12-.47 10 10 0 0 0-.14-.477 10 10 0 0 0-.172-.47 10 10 0 0 0-.197-.481 10 10 0 0 0-.201-.414 10 10 0 0 0-.252-.475 10 10 0 0 0-.238-.39 10 10 0 0 0-.301-.452 10 10 0 0 0-.291-.38 10 10 0 0 0-.315-.385 10 10 0 0 0-.347-.375 10 10 0 0 0-.363-.36 10 10 0 0 0-.334-.293 10 10 0 0 0-.434-.353 10 10 0 0 0-.393-.28 10 10 0 0 0-.4-.263 10 10 0 0 0-.461-.264 10 10 0 0 0-.436-.228 10 10 0 0 0-.437-.196 10 10 0 0 0-.48-.195 10 10 0 0 0-.69-.228 10 10 0 0 0-.094-.028 10 10 0 0 0-.066-.021 10 10 0 0 0-.461-.098 10 10 0 0 0-.482-.103 10 10 0 0 0-.418-.051 10 10 0 0 0-.575-.065 10 10 0 0 0-.144-.005A10 10 0 0 0 12 2zm-.016 5.002a5 5 0 0 1 .262.01 5 5 0 0 1 .227.011 5 5 0 0 1 .341.05 5 5 0 0 1 .135.019 5 5 0 0 1 .014.004 5 5 0 0 1 .115.025 5 5 0 0 1 .303.076 5 5 0 0 1 .265.086 5 5 0 0 1 .2.074 5 5 0 0 1 .242.106 5 5 0 0 1 .228.11 5 5 0 0 1 .196.109 5 5 0 0 1 .244.15 5 5 0 0 1 .17.12 5 5 0 0 1 .224.171 5 5 0 0 1 .186.16 5 5 0 0 1 .176.164 5 5 0 0 1 .172.18 5 5 0 0 1 .177.203 5 5 0 0 1 .133.172 5 5 0 0 1 .16.223 5 5 0 0 1 .133.214 5 5 0 0 1 .12.21 5 5 0 0 1 .107.216 5 5 0 0 1 .109.24 5 5 0 0 1 .084.223 5 5 0 0 1 .08.242 5 5 0 0 1 .07.264 5 5 0 0 1 .047.207 5 5 0 0 1 .045.277 5 5 0 0 1 .028.227 5 5 0 0 1 .02.351 5 5 0 0 1 .003.079 5 5 0 0 1-.012.271 5 5 0 0 1-.011.227 5 5 0 0 1-.05.341 5 5 0 0 1-.019.135 5 5 0 0 1-.004.014 5 5 0 0 1-.025.115 5 5 0 0 1-.076.303 5 5 0 0 1-.086.265 5 5 0 0 1-.074.2 5 5 0 0 1-.106.242 5 5 0 0 1-.11.228 5 5 0 0 1-.109.196 5 5 0 0 1-.15.244 5 5 0 0 1-.12.17 5 5 0 0 1-.171.224 5 5 0 0 1-.16.186 5 5 0 0 1-.164.176 5 5 0 0 1-.18.172 5 5 0 0 1-.203.177l-.002.002c-.018.019-.028.035-.047.053l-3.959 5.09-3.05-.979a141.684 141.684 0 0 0 3.177-3.084 5 5 0 0 1-.103-.015 5 5 0 0 1-.149-.024 5 5 0 0 1-.115-.025 5 5 0 0 1-3.57-3.04 5.072 5.072 0 0 1-.206-.661 5 5 0 0 1-.033-.147c-.025-.118-.036-.24-.054-.36-.987.993-1.964 1.993-2.954 3.05l-.98-3.053 5.092-3.957c.043-.044.082-.07.125-.11a5 5 0 0 1 .71-.634c.18-.13.367-.25.561-.356a5 5 0 0 1 .16-.08 4.94 4.94 0 0 1 .516-.222 5 5 0 0 1 .147-.057c.211-.07.43-.123.654-.164a5 5 0 0 1 .172-.027c.236-.035.476-.058.722-.059zM12 9a3 3 0 0 0-.053.002 3 3 0 0 0-.166.01 3 3 0 0 0-.133.011 3 3 0 0 0-.17.026 3 3 0 0 0-.113.021 3 3 0 0 0-.19.05 3 3 0 0 0-.103.03 3 3 0 0 0-.16.057 3 3 0 0 0-.129.053 3 3 0 0 0-.146.072 3 3 0 0 0-.12.063 3 3 0 0 0-.132.082 3 3 0 0 0-.123.08 3 3 0 0 0-.116.088 3 3 0 0 0-.126.105 3 3 0 0 0-.1.094 3 3 0 0 0-.111.111 3 3 0 0 0-.096.107 3 3 0 0 0-.094.116 3 3 0 0 0-.098.136 3 3 0 0 0-.072.11 3 3 0 0 0-.076.133 3 3 0 0 0-.07.132 3 3 0 0 0-.063.14 3 3 0 0 0-.054.14 3 3 0 0 0-.077.228 3 3 0 0 0-.007.026 3 3 0 0 0-.03.138 3 3 0 0 0-.031.149 3 3 0 0 0-.014.11 3 3 0 0 0-.02.183 3 3 0 0 0-.001.052A3 3 0 0 0 9 12a3 3 0 0 0 .002.053 3 3 0 0 0 .01.166 3 3 0 0 0 .011.133 3 3 0 0 0 .026.17 3 3 0 0 0 .021.113 3 3 0 0 0 .05.19 3 3 0 0 0 .03.103 3 3 0 0 0 .057.16 3 3 0 0 0 .053.129 3 3 0 0 0 .072.146 3 3 0 0 0 .063.12 3 3 0 0 0 .082.132 3 3 0 0 0 .08.123 3 3 0 0 0 .088.116 3 3 0 0 0 .105.126 3 3 0 0 0 .094.1 3 3 0 0 0 .111.111 3 3 0 0 0 .107.096 3 3 0 0 0 .116.094 3 3 0 0 0 .136.098 3 3 0 0 0 .11.072 3 3 0 0 0 .133.076 3 3 0 0 0 .132.07 3 3 0 0 0 .135.06 3 3 0 0 0 .153.061 3 3 0 0 0 .216.07 3 3 0 0 0 .004.003 3 3 0 0 0 .026.007 3 3 0 0 0 .138.03 3 3 0 0 0 .149.031 3 3 0 0 0 .11.014 3 3 0 0 0 .183.02 3 3 0 0 0 .011.001 3 3 0 0 0 .041 0A3 3 0 0 0 12 15a3 3 0 0 0 .053-.002 3 3 0 0 0 .166-.01 3 3 0 0 0 .133-.011 3 3 0 0 0 .17-.026 3 3 0 0 0 .113-.021 3 3 0 0 0 .19-.05 3 3 0 0 0 .103-.03 3 3 0 0 0 .16-.057 3 3 0 0 0 .129-.053 3 3 0 0 0 .146-.072 3 3 0 0 0 .12-.063 3 3 0 0 0 .132-.082 3 3 0 0 0 .123-.08 3 3 0 0 0 .116-.088 3 3 0 0 0 .126-.105 3 3 0 0 0 .1-.094 3 3 0 0 0 .111-.111 3 3 0 0 0 .096-.107 3 3 0 0 0 .094-.116 3 3 0 0 0 .098-.136 3 3 0 0 0 .072-.11 3 3 0 0 0 .076-.133 3 3 0 0 0 .07-.132 3 3 0 0 0 .06-.135 3 3 0 0 0 .061-.153 3 3 0 0 0 .07-.216 3 3 0 0 0 .003-.004 3 3 0 0 0 .007-.026 3 3 0 0 0 .03-.138 3 3 0 0 0 .031-.149 3 3 0 0 0 .002-.008 3 3 0 0 0 .012-.101 3 3 0 0 0 .02-.184 3 3 0 0 0 .001-.011 3 3 0 0 0 0-.041A3 3 0 0 0 15 12a3 3 0 0 0-.002-.053 3 3 0 0 0-.01-.166 3 3 0 0 0-.011-.133 3 3 0 0 0-.026-.17 3 3 0 0 0-.021-.113 3 3 0 0 0-.05-.19 3 3 0 0 0-.03-.103 3 3 0 0 0-.057-.16 3 3 0 0 0-.053-.129 3 3 0 0 0-.072-.146 3 3 0 0 0-.063-.12 3 3 0 0 0-.082-.132 3 3 0 0 0-.08-.123 3 3 0 0 0-.088-.116 3 3 0 0 0-.105-.126 3 3 0 0 0-.094-.1 3 3 0 0 0-.111-.111 3 3 0 0 0-.107-.096 3 3 0 0 0-.116-.094 3 3 0 0 0-.136-.098 3 3 0 0 0-.11-.072 3 3 0 0 0-.133-.076 3 3 0 0 0-.132-.07 3 3 0 0 0-.14-.063 3 3 0 0 0-.14-.054 3 3 0 0 0-.228-.077 3 3 0 0 0-.026-.007 3 3 0 0 0-.138-.03 3 3 0 0 0-.149-.031 3 3 0 0 0-.008-.002 3 3 0 0 0-.101-.012 3 3 0 0 0-.184-.02 3 3 0 0 0-.011-.001 3 3 0 0 0-.041 0A3 3 0 0 0 12 9z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 720 720" id="arduino" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="ana" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-opacity="100%" stroke-width="60" stroke="#00979c" d="M174 30a10.5 10.1 0 0 0 0 280C364 320 344 30 544 30a10.5 10.1 0 0 1 0 280C354 320 374 30 174 30"/><path d="M528 205v-32.8h-32.5v-13.7H528V126h13.9v32.5h32.5v13.7h-32.5V205H528z" text-anchor="middle" fill="#00979c" stroke-width="20" stroke="#00979c" font-family="sans-serif" font-size="167"/><path fill="#00979c" stroke="#00979c" stroke-width="23.6" transform="matrix(1.56 0 0 .64 -366 .528)" d="M321 266v-17.4h53.3V266H321z"/></symbol></defs><title>Layer 1</title><use x="20.063" y="360.85" transform="matrix(.997 0 0 .997 -18.596 -159.19)" xlink:href="#ana"/></symbol><symbol viewBox="0 0 24 24" id="assembly" xmlns="http://www.w3.org/2000/svg"><path d="M1.746 1.566v20.905H5.13v-2.088H3.438V3.656h1.69v-2.09H1.747zm17.219 0v2.09h1.693v16.727h-1.693v2.09h3.383V1.566h-3.383zM15.196 3.988c-.5 0-.93.076-1.29.225-.359.15-.652.372-.877.671-.226.302-.39.673-.494 1.108a6.715 6.715 0 0 0-.155 1.54c0 .573.049 1.083.15 1.528.1.442.264.811.49 1.11.222.298.512.524.872.676.36.153.795.23 1.304.23.518 0 .954-.075 1.308-.224.353-.153.643-.376.869-.671.219-.29.38-.661.484-1.112.104-.454.156-.967.156-1.54 0-.573-.052-1.079-.152-1.515a2.92 2.92 0 0 0-.485-1.106 2.09 2.09 0 0 0-.868-.686c-.354-.155-.79-.234-1.312-.234zm-6.814.12a.941.941 0 0 1-.138.458.849.849 0 0 1-.356.296A1.71 1.71 0 0 1 7.385 5a5.244 5.244 0 0 1-.631.037v1.11H8.19v3.6H6.754v1.188h4.545V9.745H9.894V4.11H8.382zm6.814 1.138c.375 0 .643.176.805.527.161.348.241.933.241 1.756 0 .814-.082 1.399-.247 1.756-.164.356-.43.534-.799.534-.369 0-.636-.178-.8-.534-.165-.357-.248-.941-.248-1.749 0-.829.082-1.415.243-1.763.162-.35.43-.527.805-.527zm-6.33 7.64c-.5 0-.93.073-1.29.223-.359.15-.651.374-.877.673-.225.302-.39.67-.494 1.106a6.715 6.715 0 0 0-.155 1.54c0 .573.05 1.082.15 1.527.1.442.264.814.49 1.112.222.3.514.525.874.677.36.152.793.229 1.302.229.519 0 .954-.076 1.308-.225.354-.153.643-.376.869-.672.22-.29.38-.66.484-1.111.104-.455.156-.967.156-1.54 0-.573-.05-1.079-.15-1.515a2.923 2.923 0 0 0-.487-1.106 2.084 2.084 0 0 0-.867-.686c-.353-.156-.791-.232-1.313-.232zm5.846.119a.941.941 0 0 1-.138.457.85.85 0 0 1-.356.296 1.71 1.71 0 0 1-.503.137 5.245 5.245 0 0 1-.631.037v1.112h1.435v3.597h-1.435v1.189h4.545v-1.189h-1.405v-5.636h-1.512zm-5.846 1.137c.375 0 .643.176.805.527.162.347.241.933.241 1.756 0 .813-.08 1.399-.245 1.755-.164.357-.432.534-.8.534-.37 0-.637-.177-.802-.534-.164-.356-.245-.939-.245-1.746 0-.83.08-1.418.242-1.765.161-.35.43-.527.804-.527z" fill="#ff6e40"/></symbol><symbol viewBox="0 0 24 24" id="aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="api" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apa"/><linearGradient id="apa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#apb"/><linearGradient id="apb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#apc"/><linearGradient id="apc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#apd"/><linearGradient id="apd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#ape"/><linearGradient id="ape" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#apf"/><linearGradient id="apf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apg"/><linearGradient id="apg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="app" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#aph"/><linearGradient id="aph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><g transform="rotate(11.282 -1.694 21.569) scale(.47102)" clip-rule="evenodd" fill="none" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#api)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#apj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#apk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#apl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#apm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#apn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#apo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#app)"/></g></symbol><symbol viewBox="0 0 24 24" id="autohotkey" xmlns="http://www.w3.org/2000/svg"><path d="M5 3c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm3.668 3.447a.9.9 0 0 1 .652.256.84.84 0 0 1 .262.625c0 .34-.014.852-.041 1.537-.022.68-.033 1.19-.033 1.53 0 .111-.016.326-.047.644a6.149 6.149 0 0 0-.033.68l2.578-.485c1.007-.179 1.874-.281 2.603-.308.018-.3.048-1.105.088-2.416.01-.345.115-.742.317-1.19.25-.55.533-.826.851-.826.237 0 .448.08.631.236.197.17.295.382.295.637a.775.775 0 0 1-.025.201c-.09.327-.135.612-.135.854 0 .125-.014.32-.041.584-.023.26-.033.453-.033.578 0 .425-.022 1.056-.067 1.893a38.963 38.963 0 0 0-.068 1.892c0 .327.025.816.074 1.465.05.649.074 1.136.074 1.463a.84.84 0 0 1-.261.625.893.893 0 0 1-.65.254 1 1 0 0 1-.686-.254.777.777 0 0 1-.29-.611c0-.327-.015-.818-.046-1.471a39.552 39.552 0 0 1-.041-1.47c0-.256.004-.482.013-.679-.702.032-1.57.142-2.603.33-.86.157-1.719.316-2.578.477-.01.304-.042.812-.096 1.523a22.354 22.354 0 0 0-.066 1.538.84.84 0 0 1-.262.625.893.893 0 0 1-.65.253.898.898 0 0 1-.653-.253.84.84 0 0 1-.262-.625c0-.452.038-1.128.114-2.028.08-.9.12-1.575.12-2.027 0-.573.015-1.436.042-2.586.027-1.155.04-2.017.04-2.59a.84.84 0 0 1 .263-.625.895.895 0 0 1 .65-.256z" fill="#4caf50"/></symbol><symbol viewBox="0 0 24 24" id="autoit" xmlns="http://www.w3.org/2000/svg"><defs id="ardefs8"><style id="arstyle4482">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style><style id="arstyle4510">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style></defs><g id="arg4522" transform="translate(-59.538 -26.404) scale(.0555)"><path d="M12.8 2.133A10.666 10.666 0 0 0 2.136 12.799 10.666 10.666 0 0 0 12.8 23.465a10.666 10.666 0 0 0 10.668-10.666A10.666 10.666 0 0 0 12.8 2.133zm.15 4.713c.456 0 .836.105 1.142.314.306.21.565.469.78.78l6.089 8.812H9.627l1.82-2.506h3.36c.315 0 .589.01.822.03a11.93 11.93 0 0 1-.473-.663 39.13 39.13 0 0 0-.517-.75l-1.748-2.578-4.577 6.467H4.746l6.25-8.813c.204-.281.46-.534.772-.757.31-.224.705-.336 1.181-.336z" transform="matrix(16.89188 0 0 16.89188 1072.761 475.745)" id="arcircle4514" fill="#1976d2" stroke-width=".026"/></g></symbol><symbol viewBox="0 0 213.33333 213.33333" id="babel" xmlns="http://www.w3.org/2000/svg"><path d="M50.22 199.659c-.875-.406-1.261-1.6-.857-2.652.404-1.053.12-1.914-.63-1.914s-1.615.748-1.92 1.663c-.328.983-1.27.302-2.304-1.667-.962-1.831-3.718-5.533-6.126-8.226-9.418-10.535-7.71-27.444 5.432-53.77 12.459-24.96 23.117-39.033 45.966-60.696 30.229-28.66 52.679-46.223 70.587-55.22 10.98-5.518 13.025-5.059 2.778.624-11.004 6.102-11.378 6.359-10.512 7.226.33.33 7.306-2.67 15.504-6.667 15.87-7.737 16.34-7.912 16.34-6.082 0 .652-4.95 3.738-11 6.858-13.062 6.736-12.722 6.48-10.472 7.872 1.117.69 5.428-.582 11.54-3.406 5.367-2.48 10.397-4.508 11.179-4.508 2.755 0-3.928 5.302-11.541 9.157-20.437 10.35-68.937 46.043-68.07 50.097.166.777-5.792 7.639-13.241 15.248-15.257 15.587-26.14 30.002-33.748 44.706-6.379 12.326-7.457 17.734-5.385 26.996 3.482 15.56 11.592 18.366 31.482 10.895 28.228-10.603 45.758-28.704 47.022-48.556.602-9.442-1.317-13.479-8.52-17.93-4.01-2.48-5.268-2.621-12.065-1.365-4.173.771-10.153 2.906-13.289 4.744s-6.455 3.34-7.377 3.34c-.922 0-3.216 1.336-5.096 2.968-1.88 1.633.48-1.13 5.247-6.14 6.82-7.167 7.956-8.9 5.333-8.132-5.208 1.525-10.194 4.33-15.649 8.803-2.76 2.264-.923.175 4.08-4.641 11.565-11.131 21.183-15.97 33.088-16.641 17.097-.966 27.254 5.805 31.964 21.31 2.435 8.017 2.609 10.24 1.353 17.37-1.65 9.361-7.034 21.553-15.593 35.307-4.398 7.067-8.434 11.427-15.588 16.844-9.166 6.94-15.654 11.02-15.654 9.845 0-.295 2.455-2.161 5.455-4.147 8.818-5.835 5.075-5.377-8.326 1.02-6.854 3.27-15.199 6.593-18.542 7.38-7.106 1.675-30.527 3.164-32.846 2.089zm-8.408-19.899c0-1.1-.6-2-1.333-2-.734 0-1.334.9-1.334 2s.6 2 1.333 2c.734 0 1.334-.9 1.334-2zm89.255-8.204c1.53-1.945 2.473-3.845 2.097-4.222-.377-.377-.836-.435-1.02-.13-.182.306-1.787 2.206-3.565 4.223-1.778 2.016-2.571 3.666-1.763 3.666s2.72-1.591 4.25-3.536zm-77.644-1.745c-.82-2.172-1.74-3.7-2.045-3.396-.951.952 1.088 7.345 2.343 7.345.656 0 .522-1.777-.298-3.95zm82.303-27.915c-.837-.837-3.217 2.55-3.184 4.53.012.734.896.178 1.965-1.235 1.07-1.413 1.618-2.896 1.219-3.295zm-66.238-36.904c-1.312-1.312-3.676.702-3.676 3.133 0 2.035.175 2.031 2.254-.047 1.24-1.24 1.88-2.628 1.422-3.086zm39.657.768c4.403-2.196 6.8-3.986 5.333-3.982-2.838.01-16.667 6.028-16.667 7.254 0 1.6 3.717.527 11.333-3.272zm16.667-5.333c0-.733-.9-1.333-2-1.333s-2 .6-2 1.333.9 1.333 2 1.333 2-.6 2-1.333zm-3.334-3.923l5.334-1.104-7.334-.133c-4.033-.073-8.233.45-9.333 1.16-2.539 1.64 3.572 1.682 11.333.077zm35.738-63.976c2.788-1.69 4.765-3.376 4.393-3.748-.947-.947-11.942 5.654-14.237 8.548-1.792 2.258-1.714 2.276 1.44.329a1452.76 1452.76 0 0 1 8.403-5.13z" fill="#ffca28" stroke-width="1.333"/></symbol><symbol viewBox="0 0 400 400" fill-opacity=".05" id="bithound" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.88 0 0 .88 24.121 2.895)" fill="#e53935" fill-opacity="1"><path d="M370.5 207c-1.5-14.8-4.8-29.9-9.5-44-13.5-40.3-38.6-81.6-70.3-110.1-1.4-1.2-6.7-4.4-8.7-3.3-5.2 2.9 4.6 22.8 5.8 26.4 7.4 22 12.1 45.3 6.8 68.3-7.1 30.4-30.4 51.7-61.5 54.3-17.1 1.4-34.3-.5-51.4 1.5-25.6 3-51.7 11.8-68 32.8-1.9 2.4-3.6 5.1-5.2 7.9h-.4c-6.3.7-12.6-2-15.7-3.7-.8-.5-1.6-.9-2.2-1.2-19-10.5-33-34-41.6-53.4-3.9-9-7.2-18.4-9.3-27.9-1-4.3-1.1-8.8-1.3-13.2-.1-2.7.3-6.5-1.2-8.9-3.3-5.2-7.5-.2-8.2 4-1.1 6.9-2.1 13.7-1.8 20.7.5 11.8 3.8 23.5 8 34.5 6.2 16.2 14.9 31.1 26.2 44.4 4.7 5.5 9.7 10.6 15.1 15.3 4.8 4.3 10.9 7.7 14.5 13.2 4.2 6.3 4.9 14.1 4.5 21.4-1 19.3-1.6 37.4 3.9 56.2 4.8 16.7 10.8 33.8 20.8 48.1 5 7.1 11.2 14.6 18 19.9 4.6 3.6 13.3 4 8.3-9.2-11.1-29.3-12.1-59.7 5.2-87.1 14.5-22.8 40.1-43.1 69-39.5 42.5 5.3 72.1 44.3 70 86-.6 11.7-1 21.7-4.7 32.7-1.5 4.4-2.6 10-1.5 14.6 1.8 7.8 10.5 4.9 14.3-.2 10.3-14 21.1-27.6 30.8-42 31.6-47.2 47-101.8 41.3-158.5z"/><path d="M132.4 92.1c.7 2.3 1.4 4.8 1.9 7.5.1 1.1.4 2.3 1 3.4 2.6 6.8 8.9 10.5 14.8 14 3.6 2.2 10.1 4.3 14.1 5.9 5.2 2.1 16.4-.6 21.7-1 12.2-1 23.5-5.3 34.7 1.2-57.4 67.3-3.2 82.3 38.8 49.9 48-37 2.8-124.3 2.8-124.3s-1-6.8-19.2-10.8c-1.7-.9-3.4-1.7-5.1-2.4-18-8.3-34.2 5.3-47.2 16.4-3.8 3.2-7.5 6.4-11.5 9.4-5.4 4-11.2 7.3-17.3 10.2-6.4 3-14 6.4-21.1 6.7-1 0-2.9.2-4.9.6-3.1.3-4.7 1.1-5.4 2.5-1.2 1-2 2.4-1.8 4.2.2 2.5 1.4 4.6 2.7 6.2.4.1.7.3 1 .4z"/></g></symbol><symbol viewBox="0 0 400.00001 399.99999" id="bower" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12.061 33.203) scale(.81733)"><path d="M447.61 200.08c-23.139-22.234-138.85-36.114-175.36-40.154a107.137 107.137 0 0 0 4.517-12.944 146.107 146.107 0 0 1 15.905-5.901c.677 1.997 3.865 9.648 5.682 13.279 73.415 2.025 77.184-54.557 80.17-70.058 2.92-15.157 2.771-29.802 27.953-56.575-37.516-10.933-91.467 16.945-109.54 58.437-6.79-2.545-13.597-4.424-20.328-5.586-4.824-19.46-29.944-73.672-95.863-73.672-83.46 0-174.43 68.853-174.43 185.41 0 97.976 66.891 183.84 104.68 183.84 16.505 0 30.703-12.36 34.036-23.44 2.795 7.597 11.368 31.213 14.184 37.225 4.162 8.89 23.41 16.583 31.833 7.357 10.83 6.017 30.703 9.641 41.534-6.405 20.86 4.412 39.3-8.026 39.702-22.868 10.235-.546 15.256-14.918 13.021-26.363-1.647-8.426-19.248-38.66-26.113-49.098 13.59 11.054 48.013 14.183 52.194.007 21.911 17.198 56.057 8.171 58.765-5.815 26.624 6.917 57.16-8.276 52.146-26.676 42.771-2.958 37.296-48.464 25.296-59.996z" fill="#543729" stroke-width=".973"/><path d="M328.514 103.025c9.212-18.277 20.788-38.234 35.409-50.58-16.093 6.485-31.981 25.873-41.375 46.595a144.914 144.914 0 0 0-14.552-8.132c13.105-27.972 43.555-51.332 77.112-53.157-22.477 20.385-14.498 62.754-32.979 85.183-5.288-5.311-17.43-15.562-23.615-19.909zm-14.53 29.762c.01-.7.272-6.094.763-8.557-1.288-.304-9.3-1.87-13.476-1.772-.304 5.245 2.204 14.17 4.684 19.541 17.075-.358 29.408-5.471 36.667-10.172-6.18-2.88-16.726-5.442-24.745-6.974-.894 1.851-3.097 6.568-3.892 7.934z" fill="#00acee"/><g stroke-width=".973"><path d="M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z" fill="#2baf2b"/><path d="M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z" fill="#ffcc2f"/><path d="M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a67.843 67.843 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z" fill="#cecece"/><path d="M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z" fill="#ef5734"/><path d="M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191c0-21.644-17.548-39.191-39.192-39.191-21.644 0-39.19 17.547-39.19 39.191z" fill="#ffcc2f"/><path d="M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464z" fill="#543729"/><ellipse cx="180.22" cy="98.044" rx="13.673" ry="8.501" fill="#fff"/></g></g></symbol><symbol viewBox="0 0 140 140" id="browserlist" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><path d="M70.314 10.066a59.828 59.828 0 0 0-59.828 59.828 59.828 59.828 0 0 0 59.828 59.828 59.828 59.828 0 0 0 59.828-59.828 59.828 59.828 0 0 0-59.828-59.828zm-4.836 8.785c.496 4.043 1.352 7.322 2.572 10.223 4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.128 5.128 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435v.002c-.487 1.055-2.002 2.343-3.497 3.219-4.075 2.39-11.172 5.736-20.914 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.083.197-7.485-.402-10.591-5.582 3.218-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.678-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.391 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zm26.465 19.557c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67.143 67.143 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.428-10.164-7.412-16.281z" fill="#ffca28" stroke-width=".855"/></symbol><symbol viewBox="0 0 140 140" id="browserlist_light" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><g transform="translate(10.823 10.1)" stroke-width=".855"><circle cx="59.492" cy="59.795" r="59.828" fill="#ffca28"/><path d="M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.128 5.128 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.275.867-4.613 2.704-10.256 5.019-16.871 6.506-1.071.24-2.154.458-3.25.649-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732z" fill="#37474f"/></g></symbol><symbol viewBox="0 0 24 24" id="bucklescript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm14.1 8.858a5.5 5.5 0 0 1 1.26.145c.417.093.778.213 1.082.357v1.723h-.18a3.281 3.281 0 0 0-.959-.603 2.867 2.867 0 0 0-1.155-.247c-.14 0-.277.011-.416.035a1.4 1.4 0 0 0-.395.12.756.756 0 0 0-.291.231.54.54 0 0 0-.123.348c0 .198.065.35.196.456.13.104.376.2.738.288.237.057.466.11.683.164.22.054.455.128.706.222.496.188.86.444 1.095.77.238.32.357.738.357 1.253 0 .737-.271 1.336-.813 1.798-.538.46-1.27.689-2.197.689a5.447 5.447 0 0 1-1.402-.161 6.725 6.725 0 0 1-1.117-.416v-1.794h.183c.344.318.73.563 1.155.734.429.17.839.256 1.233.256.1 0 .235-.01.4-.03.166-.02.3-.055.403-.102a.97.97 0 0 0 .313-.225c.084-.09.127-.223.127-.4a.568.568 0 0 0-.183-.424c-.119-.12-.294-.213-.526-.276-.243-.067-.5-.128-.773-.185a5.523 5.523 0 0 1-.76-.227c-.544-.204-.936-.48-1.177-.828-.237-.351-.357-.786-.357-1.305 0-.697.27-1.265.81-1.703.54-.442 1.235-.663 2.083-.663zm-8.981.135h2.51c.521 0 .903.02 1.143.06.243.041.484.13.721.266.246.144.43.338.548.583.121.24.181.518.181.83 0 .36-.082.68-.247.959a1.697 1.697 0 0 1-.7.642v.04c.423.098.758.298 1.004.603.249.305.373.706.373 1.205 0 .361-.063.686-.19.97-.125.285-.296.52-.516.707a2.31 2.31 0 0 1-.845.472c-.304.094-.69.141-1.159.141H8.12v-7.478zm1.659 1.372v1.582h.262c.263 0 .486-.007.672-.017.185-.01.332-.043.44-.1.15-.077.248-.175.294-.295.046-.124.07-.266.07-.427a.91.91 0 0 0-.083-.371.518.518 0 0 0-.282-.277 1.187 1.187 0 0 0-.456-.086c-.18-.007-.433-.01-.76-.01h-.157zm0 2.873V18.1H9.9c.469 0 .804-.002 1.007-.006.202-.003.39-.046.56-.13a.712.712 0 0 0 .357-.33c.067-.142.099-.302.099-.483 0-.237-.04-.42-.121-.547-.078-.13-.214-.228-.405-.291a1.842 1.842 0 0 0-.538-.072 49.47 49.47 0 0 0-.716-.003h-.366z" fill="#26a69a" stroke-width="1.067"/></symbol><symbol viewBox="0 0 24 24" id="c" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 15.97l.42 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96-1.14-1.27-1.68-2.88-1.68-4.83C6 9.9 6.68 8.13 8 6.89 9.28 5.64 10.92 5 12.9 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.6 2.49-1.04-.34c-.4-.1-.87-.15-1.4-.15-1.15-.01-2.11.36-2.86 1.1-.76.73-1.14 1.85-1.18 3.34.01 1.36.37 2.42 1.08 3.2.71.77 1.7 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.09-.32z" fill="#0277bd"/></symbol><symbol viewBox="0 0 300 300" id="cabal" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -822.52)" fill-rule="evenodd" color="#000"><rect transform="matrix(-.98339 .18149 .60192 .79856 0 0)" x="405.55" y="967.22" width="107.25" height="156.59" rx="12.306" ry="12.31" fill="#2d9bbd"/><rect transform="matrix(-.98528 .17093 -.59175 .80612 0 0)" x="-1156.5" y="1461.9" width="108.34" height="123.15" rx="10.69" ry="12.31" fill="#4a4bcd"/><path d="M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932c2.72-1.658 32.808 2.503 34.15-1.01 1.343-3.515-18.174-35.352-18.721-39.381-.548-4.03 9.732-40.12 7.502-43.125-2.229-3.005-30.06 9.427-33.118 8.594-3.059-.833-26.793-27.3-29.514-25.643-2.72 1.657-.405 41.177-1.747 44.693z" fill="#2e5bc1"/></g></symbol><symbol viewBox="0 0 24 24" id="cake" xmlns="http://www.w3.org/2000/svg"><path d="M12.254 6.621a1.807 1.807 0 0 0 1.808-1.807c0-.344-.09-.66-.262-.932l-1.546-2.684-1.546 2.684a1.72 1.72 0 0 0-.262.932 1.808 1.808 0 0 0 1.808 1.807m4.158 9.04l-.967-.976-.976.976c-1.175 1.166-3.236 1.175-4.42 0l-.959-.976-.994.976a3.134 3.134 0 0 1-3.977.353v4.167a.904.904 0 0 0 .904.904h14.463a.904.904 0 0 0 .904-.904v-4.167a3.134 3.134 0 0 1-3.977-.353m1.265-6.328h-4.52V7.525H11.35v1.808H6.83a2.712 2.712 0 0 0-2.711 2.712v1.392c0 .977.795 1.772 1.771 1.772.489 0 .94-.18 1.248-.515l1.952-1.926 1.908 1.926c.669.669 1.835.669 2.504 0l1.916-1.926 1.944 1.926c.316.334.768.515 1.247.515.976 0 1.78-.795 1.78-1.772v-1.392a2.712 2.712 0 0 0-2.711-2.712z" fill="#ff7043" stroke-width=".904"/></symbol><symbol viewBox="0 0 24 24" id="certificate" xmlns="http://www.w3.org/2000/svg"><path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3 3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2 3-2v3.5l3 1.5-3 1.5V15l-3-2-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2z" fill="#ff5722"/></symbol><symbol viewBox="0 0 24 24" id="changelog" xmlns="http://www.w3.org/2000/svg"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="clojure" xmlns="http://www.w3.org/2000/svg"><path d="M3.355 1.78c-.845 0-1.525.68-1.525 1.525v17.441c0 .845.68 1.525 1.525 1.525h17.442c.845 0 1.525-.68 1.525-1.525V3.305c0-.845-.68-1.526-1.525-1.526H3.355zm6.168 2.572h1.963l6.368 14.931H15.93l-3.38-8.086-3.349 8.086H7.21l4.346-10.38-2.032-4.551z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="cmake" xmlns="http://www.w3.org/2000/svg"><path d="M11.99 2.965L2.977 20.999l9.874-8.47-.863-9.564z" fill="#1e88e5"/><path d="M12.007 2.963l.002.29 1.312 14.498-.001.006.023.26 7.362 2.979h.416l-.158-.311-.114-.228h-.002l-8.84-17.494z" fill="#e53935"/><path d="M8.607 16.11L2.98 20.995h17.743v-.016L8.607 16.11z" fill="#7cb342"/></symbol><symbol class="bfmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate" xmlns="http://www.w3.org/2000/svg"><path class="bfsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#eee" stroke-width="2.849"/></symbol><symbol class="bgmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate_light" xmlns="http://www.w3.org/2000/svg"><path class="bgsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#455a64" stroke-width="2.849"/></symbol><symbol viewBox="0 0 24 24" id="coffee" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="coldfusion" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(90)" x="2.283" y="-21.86" width="19.487" height="19.487" ry="0" fill="#0d3858" stroke="#4dd0e1" stroke-width=".7"/><text x="6.653" y="16.426" fill="#4dd0e1" font-family="Calibri" font-size="29.001" font-weight="bold" letter-spacing="0" stroke-width=".725" word-spacing="0" style="line-height:1.25"><tspan x="6.653" y="16.426" font-family="'Segoe UI'" font-size="10.634" font-weight="normal">C<tspan font-size="11.844">f</tspan></tspan></text></symbol><symbol viewBox="0 0 24 24" id="conduct" xmlns="http://www.w3.org/2000/svg"><path d="M10 17l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#cddc39"/></symbol><symbol viewBox="0 0 24 24" id="console" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></symbol><symbol viewBox="0 0 24 24" id="contributing" xmlns="http://www.w3.org/2000/svg"><path d="M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="cpp" xmlns="http://www.w3.org/2000/svg"><path d="M10.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C1.56 15.77 1 14.16 1 12.21c.05-2.31.72-4.08 2-5.32C4.32 5.64 5.96 5 7.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M11 11h2V9h2v2h2v2h-2v2h-2v-2h-2v-2m7 0h2V9h2v2h2v2h-2v2h-2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="credits" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v2H3V3m4 4h10v2H7V7m-4 4h18v2H3v-2m4 4h10v2H7v-2m-4 4h18v2H3v-2z" fill="#9ccc65"/></symbol><symbol viewBox="0 0 200 200" id="crystal" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" stroke-width="1.153" fill="#cfd8dc"/></symbol><symbol viewBox="0 0 200 200" id="crystal_light" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" fill="#37474f" stroke-width="1.153"/></symbol><symbol viewBox="0 0 24 24" id="csharp" xmlns="http://www.w3.org/2000/svg"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M13.89 19l.61-4H13l.34-2h1.5l.32-2h-1.5L14 9h1.5l.61-4h2l-.61 4h1l.61-4h2l-.61 4H22l-.34 2h-1.5l-.32 2h1.5L21 15h-1.5l-.61 4h-2l.61-4h-1l-.61 4h-2m2.95-6h1l.32-2h-1l-.32 2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="css" xmlns="http://www.w3.org/2000/svg"><path d="M5 3l-.65 3.34h13.59L17.5 8.5H3.92l-.66 3.33h13.59l-.76 3.81-5.48 1.81-4.75-1.81.33-1.64H2.85l-.79 4 7.85 3 9.05-3 1.2-6.03.24-1.21L21.94 3H5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="css-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#42a5f5"/><path d="M4.676 3l-.488 2.51h10.211l-.33 1.623H3.864l-.496 2.502H13.58l-.57 2.863-4.119 1.36-3.569-1.36.248-1.232H3.06l-.593 3.005 5.898 2.254 6.8-2.254.902-4.53.18-.91L17.406 3H4.675z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 33 33" id="cucumber" xmlns="http://www.w3.org/2000/svg"><title>cucumber-mark-transparent-pips</title><g transform="translate(0 -5)" fill="none" fill-rule="evenodd"><path d="M-4-1h40v40H-4z"/><path d="M16.641 7.092c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64C21.824 34.77 28.561 28.73 29.063 20.8c.303-4.772-2.076-9.644-6.09-12.01a10.575 10.575 0 0 0-1.455-.728l-.243-.097c-.223-.082-.448-.175-.68-.242a12.614 12.614 0 0 0-3.954-.632zm2.62 4.707a1.387 1.387 0 0 0-1.213.485c-.233.31-.379.611-.534.923-.466 1.087-.31 2.251.388 3.105 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.404-1.359-1.479zm-5.654.073c-.708.068-1.382.63-1.382 1.407 0 .31.087.709.243 1.02.466 1.086 1.46 1.78 2.546 2.013.621-.854.782-2.018.316-3.105-.155-.311-.3-.617-.534-.85a1.364 1.364 0 0 0-1.188-.485zm-3.809 3.735c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.559 1.087.466 2.344.306 3.275-.316-.233-1.009-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.009-.161-.004-.243 0zm11.961 4.708a3.551 3.551 0 0 0-2.013.582c.233 1.01 1.023 1.936 2.11 2.401.389.156.705.244 1.093.244 1.397.077 2.08-1.65.994-2.427-.31-.233-.611-.379-.922-.534a3.354 3.354 0 0 0-1.262-.266zm-10.603.072a3.376 3.376 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402.388 0 .782-.087 1.092-.243 1.087-.465 1.859-1.392 2.014-2.401a3.474 3.474 0 0 0-1.99-.582zm3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.995-.077 1.32 1.724 2.028 2.5 1.02.233-.312.378-.613.534-.923.466-1.01.306-2.174-.316-3.106zm2.887.073c-.621.854-.781 2.019-.315 3.106.155.31.3.615.534.848.854.932 2.65.243 2.572-.921 0-.31-.088-.71-.243-1.02-.466-1.087-1.46-1.78-2.547-2.013z" fill="#4caf50" stroke-width=".776"/></g></symbol><symbol id="cuda" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style>.bust0{fill:#76b900}</style><title>NVIDIA-Logo</title><path id="buEye_Mark" class="bust0" d="M76.362 75.199V64.116c1.095-.068 2.19-.137 3.284-.137 30.377-.958 50.286 26.135 50.286 26.135s-21.483 29.83-44.539 29.83c-3.079 0-6.089-.48-8.962-1.438v-33.66c11.836 1.436 14.23 6.636 21.277 18.471l15.804-13.273s-11.562-15.12-30.992-15.12c-2.053-.068-4.105.069-6.158.274m0-36.67v16.556l3.284-.205c42.213-1.437 69.784 34.618 69.784 34.618s-31.608 38.45-64.516 38.45c-2.873 0-5.678-.274-8.483-.753v10.262c2.326.274 4.72.48 7.046.48 30.65 0 52.817-15.668 74.3-34.14 3.558 2.874 18.13 9.784 21.14 12.794-20.388 17.104-67.937 30.856-94.893 30.856-2.6 0-5.062-.137-7.525-.41v14.436h116.44V38.532zm0 79.977v8.757C48.038 122.2 40.17 92.712 40.17 92.712s13.615-15.05 36.192-17.514v9.579h-.068c-11.836-1.437-21.14 9.646-21.14 9.646s5.268 18.678 21.209 24.082M26.077 91.481S42.839 66.714 76.43 64.115v-9.03C39.213 58.094 7.057 89.565 7.057 89.565s18.199 52.68 69.305 57.47v-9.579c-37.492-4.652-50.286-45.975-50.286-45.975z" fill="#8bc34a" stroke-width=".684"/></symbol><symbol viewBox="0 0 24 24" id="dart" xmlns="http://www.w3.org/2000/svg"><title>Dart</title><path d="M12.486 1.385a.978.978 0 0 0-.682.281l-.01.007-6.387 3.692 6.371 6.372v.004l7.659 7.659 1.46-2.63-5.265-12.64-2.456-2.457a.972.972 0 0 0-.69-.288z" fill="#00ca94"/><path d="M5.422 5.35L1.73 11.733l-.007.01a.967.967 0 0 0 .006 1.371l3.059 3.061 11.963 4.706 2.704-1.502-.073-.073-.018.002-7.5-7.512h-.01L5.423 5.35z" fill="#1565c0"/><path d="M5.405 5.353l6.518 6.525h.01l7.502 7.51 2.855-.544.005-8.449-3.016-2.955c-.66-.647-1.675-1.064-2.695-1.202l.002-.032-11.181-.853z" fill="#1565c0"/><path d="M5.414 5.361l6.521 6.522v.009l7.506 7.506-.546 2.855h-8.448l-2.954-3.017c-.647-.66-1.064-1.676-1.2-2.696l-.033.003L5.414 5.36z" fill="#00ee94"/></symbol><symbol viewBox="0 0 24 24" id="database" xmlns="http://www.w3.org/2000/svg"><path d="M12 3C7.58 3 4 4.79 4 7s3.58 4 8 4 8-1.79 8-4-3.58-4-8-4M4 9v3c0 2.21 3.58 4 8 4s8-1.79 8-4V9c0 2.21-3.58 4-8 4s-8-1.79-8-4m0 5v3c0 2.21 3.58 4 8 4s8-1.79 8-4v-3c0 2.21-3.58 4-8 4s-8-1.79-8-4z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="diff" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h2v-2H3V3h11v2h2V3c0-1.11-.89-2-2-2H3m6 6c-1.11 0-2 .89-2 2v2h2V9h2V7H9m4 0v2h1v1h2V7h-3m5 0v2h2v11H9v-2H7v2c0 1.11.89 2 2 2h11c1.11 0 2-.89 2-2V9c0-1.11-.89-2-2-2h-2m-4 5v2h-2v2h2c1.11 0 2-.89 2-2v-2h-2m-7 1v3h3v-2H9v-1H7z" fill="#42a5f5"/></symbol><symbol id="docker" viewBox="0 0 41 34.5" xmlns="http://www.w3.org/2000/svg"><style id="bystyle2">.byst0{fill:#fff}.byst1{clip-path:url(#bySVGID_4_)}</style><g id="byg34" transform="translate(.292 1.9)" fill="#0087c9"><g id="byg32"><g id="byg30"><g id="byg28"><g id="byg26"><g id="byg9"><path id="bySVGID_1_" class="byst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g></g></g></g></g></g></symbol><symbol viewBox="0 0 24 24" id="document" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z" fill="#42a5f5"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone_light" xmlns="http://www.w3.org/2000/svg"><g fill="#424242" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol viewBox="0 0 3473 3473" id="editorconfig" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><defs id="ccdefs4"><style id="ccstyle2">.ccfil2{fill:#020202}.ccfil0{fill:#e3e3f8}.ccfil5{fill:#efefef}.ccfil6{fill:#faf1f1}.ccfil3{fill:#fdf2f2}.ccfil1{fill:#fdfdfd}.ccfil4{fill:#fef3f3}</style></defs><g id="ccLayer_x0020_1" transform="matrix(.8945 0 0 .8945 138.649 275.985)"><g id="cc_631799120"><g id="ccg11"><path class="ccfil0" d="M967 1895c46-30 84-105 61-158-63 27-60 89-61 158z" id="ccpath7" fill="#e3e3f8"/><path class="ccfil0" d="M1679 2067c50-16 98-72 71-130-39 27-64 64-71 130z" id="ccpath9" fill="#e3e3f8"/></g><g id="ccg21"><path class="ccfil1" d="M280 2895c0 63 16 131 60 155 162 91 730 20 923-23 101-23 183-98 278-139 214-93 369-168 540-293 124-91 321-347 342-500l-169-38c-4 172-43 211-196 251-103 28-304 34-409 16-139-23-202-96-265-179-122-162 27-275-166-286-203 249-561 70-718 45-67 97-224 727-222 871 97-33 158 3 245 37 308 119 39 224-84 193-84-20-110-75-159-110z" id="ccpath13" fill="#fdfdfd"/><path class="ccfil1" d="M683 1458c125 24 236 76 342 129 173 86 204 74 220 194 2 22-2 34 61 54 106 33-61-26 223-25 169 1 556 69 681 148 52 33 42 75 218 70-2-207-57-516-138-706-99-230-230-265-497-351-156-50-614-105-756-17-133 83-158 182-282 356-36 51-49 90-72 148z" id="ccpath15" fill="#fdfdfd"/><path class="ccfil1" d="M1784 1883c100 41-5 306-144 242-45-127 62-199 91-256-60-9-231-36-282-17-66 25-81 166-47 232 160 314 867 247 792 3-30-99-58-115-159-149-81-27-162-55-251-55z" id="ccpath17" fill="#fdfdfd"/><path class="ccfil1" d="M527 1848c80 77 261 89 378 95 15-155 28-271 152-262 61 83 29 181-35 244 109-1 172-83 156-202-92-66-371-198-511-217-39 42-135 272-140 342z" id="ccpath19" fill="#fdfdfd"/></g><path class="ccfil2" d="M339 2838c66-6 238 44 252 100-107 13-243 3-252-100zm-59 57c49 35 75 90 159 110 123 31 392-74 84-193-87-34-148-70-245-37-2-144 155-774 222-871 157 25 515 204 718-45 193 11 44 124 166 286 63 83 126 156 265 179 105 18 306 12 409-16 153-40 192-79 196-251l169 38c-21 153-218 409-342 500-171 125-326 200-540 293-95 41-177 116-278 139-193 43-761 114-923 23-44-24-60-92-60-155zm1399-828c7-66 32-103 71-130 27 58-21 114-71 130zm105-184c89 0 170 28 251 55 101 34 129 50 159 149 75 244-632 311-792-3-34-66-19-207 47-232 51-19 222 8 282 17-29 57-136 129-91 256 139 64 244-201 144-242zm-817 12c1-69-2-131 61-158 23 53-15 128-61 158zm-440-47c5-70 101-300 140-342 140 19 419 151 511 217 16 119-47 201-156 202 64-63 96-161 35-244-124-9-137 107-152 262-117-6-298-18-378-95zm-100-80c-37-102-37-261 120-274l-80 223c-21 48-21 37-40 51zm256-310c23-58 36-97 72-148 124-174 149-273 282-356 142-88 600-33 756 17 267 86 398 121 497 351 81 190 136 499 138 706-176 5-166-37-218-70-125-79-512-147-681-148-284-1-117 58-223 25-63-20-59-32-61-54-16-120-47-108-220-194-106-53-217-105-342-129zm1770-49c-19-63 16-59 77-102 35-25 63-51 106-75 161-90 461-105 589 2 52 43 137 127 124 237-27 219-177 339-300 439-125 102-333 207-548 137-18-44-4-323-25-426-19-92-9-102 44-157 156-162 494-280 686-141 81 60 58 83 100 129 52-56-45-244-403-232-243 8-348 198-450 189zM997 840c5-139 133-427 261-527 155-120 317-233 555-98 59 33 56 50 62 132 5 79-2 108-22 172-158 510-290 217-796 338 19-166 163-314 243-391 137-133 236-219 442-191 57 95 63 155-6 266-92 148-115 139-101 240 72-18 94-88 127-158 201-420-91-471-270-394-120 51-334 287-404 429-14 28-29 64-42 95zm792 21c21-125 145-156 145-541 0-166-204-315-471-204-229 94-264 166-386 350-115 174-111 365-210 526-29 46-55 62-87 108-23 34-40 77-63 117-47 77-95 133-133 225-120 3-221 5-233 129-16 170 64 212 64 276-1 69-281 765-203 1180 22 114 97 115 217 129 289 35 664 23 923-81l470-225c119-67 319-194 408-287 63-65 96-120 150-197 74-108 76-106 92-253 98 18 281 61 342 114-7 69-41 36-41 98 39 1 104-48 120-102-41-60-84-50-143-98 47-37 132-54 197-81 140-58 379-234 438-394 47-129 12-344-64-428-80-88-266-133-418-133-181 0-368 130-514 186-56-49-60-105-101-159-47-64-353-224-499-255z" id="ccpath23" fill="#020202"/><path class="ccfil3" d="M2453 1409c102 9 207-181 450-189 358-12 455 176 403 232-42-46-19-69-100-129-192-139-530-21-686 141-53 55-63 65-44 157 21 103 7 382 25 426 215 70 423-35 548-137 123-100 273-220 300-439 13-110-72-194-124-237-128-107-428-92-589-2-43 24-71 50-106 75-61 43-96 39-77 102z" id="ccpath25" fill="#fdf2f2"/><path class="ccfil4" d="M997 840l49-87c13-31 28-67 42-95 70-142 284-378 404-429 179-77 471-26 270 394-33 70-55 140-127 158-14-101 9-92 101-240 69-111 63-171 6-266-206-28-305 58-442 191-80 77-224 225-243 391 506-121 638 172 796-338 20-64 27-93 22-172-6-82-3-99-62-132-238-135-400-22-555 98-128 100-256 388-261 527z" id="ccpath27" fill="#fef3f3"/><path class="ccfil5" d="M427 1768c19-14 19-3 40-51l80-223c-157 13-157 172-120 274z" id="ccpath29" fill="#efefef"/><path class="ccfil6" d="M591 2938c-14-56-186-106-252-100 9 103 145 113 252 100z" id="ccpath31" fill="#faf1f1"/></g></g></symbol><symbol viewBox="0 0 24 24" id="elixir" xmlns="http://www.w3.org/2000/svg"><path d="M12.431 22.383c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.774-8.172 4.916-10.91 1.014-1.295 2.931-2.321 2.931-2.321s-.982 5.238 1.683 7.318c2.365 1.847 4.105 4.25 4.105 6.363 0 4.232-2.784 7.68-6.645 7.68z" fill="#9575cd" stroke-width="1.256"/></symbol><symbol viewBox="0 0 323.00001 322.99999" id="elm" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.8053 0 0 .8053 30.106 31.524)"><path fill="#f0ad00" d="M160.8 153.865l68.028-68.03H92.77z"/><path fill="#7fd13b" d="M160.983 5.098H12.033l68.524 68.525H229.51z"/><path fill="#7fd13b" stroke-width=".974" d="M243.906 88.021l74.136 74.137-74.474 74.475-74.137-74.137z"/><path fill="#60b5cc" d="M318.2 145.045V5.098H178.252z"/><path fill="#5a6378" d="M152.164 162.499L3.4 13.733v297.533z"/><path fill="#f0ad00" d="M252.205 245.27l65.995 65.996v-131.99z"/><path fill="#60b5cc" d="M160.8 171.134L12.034 319.899h297.53z"/></g></symbol><symbol viewBox="0 0 24 24" id="email" xmlns="http://www.w3.org/2000/svg"><path d="M20 8l-8 5-8-5V6l8 5 8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 30 30" id="erlang" xmlns="http://www.w3.org/2000/svg"><path style="line-height:1.25;-inkscape-font-specification:'Wide Latin'" d="M5.217 4.367c-.048.052-.097.1-.144.153C2.697 7.182 1.51 10.798 1.51 15.366c0 4.418 1.156 7.862 3.46 10.34h19.414c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52-1.454 1.381c-.866.773-.845.931-2.314 1.78-1.496.674-3.04.966-4.634.966-2.516 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.097-6.68l17.458.067-.182-1.472s-.847-7.129-2.542-9.372zm8.76.846c1.565 0 3.22.535 3.96 1.471.742.937.932 1.667.974 3.524H9.12c.111-1.955.436-2.81 1.372-3.697.937-.888 2.03-1.298 3.484-1.298z" font-weight="400" font-size="48" font-family="Wide Latin" letter-spacing="0" word-spacing="0" fill="#f44336" stroke-width=".97"/></symbol><symbol viewBox="0 0 299.99999 300.00001" id="eslint" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2.88 18.438) scale(1.0344)"><path d="M97.021 99.016l48.432-27.962c1.212-.7 2.706-.7 3.918 0l48.433 27.962a3.92 3.92 0 0 1 1.959 3.393v55.924a3.924 3.924 0 0 1-1.959 3.394l-48.433 27.962c-1.212.7-2.706.7-3.918 0l-48.432-27.962a3.92 3.92 0 0 1-1.959-3.394v-55.924a3.922 3.922 0 0 1 1.959-3.393" fill="#7986cb"/><path d="M273.34 124.49L215.473 23.82c-2.102-3.64-5.985-6.325-10.188-6.325H89.545c-4.204 0-8.088 2.685-10.19 6.325L21.488 124.27c-2.102 3.641-2.102 8.236 0 11.877l57.867 99.847c2.102 3.64 5.986 5.501 10.19 5.501h115.74c4.203 0 8.087-1.805 10.188-5.446l57.867-100.01c2.104-3.639 2.104-7.907.001-11.547m-47.917 48.41c0 1.48-.891 2.849-2.174 3.59l-73.71 42.527a4.194 4.194 0 0 1-4.17 0l-73.767-42.527c-1.282-.741-2.179-2.109-2.179-3.59V87.847c0-1.481.884-2.849 2.167-3.59l73.707-42.527a4.185 4.185 0 0 1 4.168 0l73.772 42.527c1.283.741 2.186 2.109 2.186 3.59z" fill="#3f51b5"/></g></symbol><symbol viewBox="0 0 24 24" id="exe" xmlns="http://www.w3.org/2000/svg"><path d="M19 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14m0 14V8H5v10h14z" fill="#e64a19"/></symbol><symbol viewBox="0 0 24 24" id="favicon" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="file" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 400 400" id="firebase" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 103)"><path d="M72.55 208.77l44.456-292.29 56.209 90.445L195.49-37.57 330.6 209.28z" fill="#ffa712"/><path d="M195.7 276.73l134.9-67.45-46.5-224.83L72.55 208.77z" fill="#fcca3f"/><path d="M173.22 6.932L72.56 208.772l136.35-144.58z" fill="#f6820c"/></g></symbol><symbol viewBox="0 0 24 24" id="flash" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cma"><stop offset="0" stop-color="#d92f3c"/><stop offset="1" stop-color="#791223"/></linearGradient><linearGradient xlink:href="#cma" id="cmb" x1="2.373" y1="12.027" x2="21.86" y2="12.027" gradientUnits="userSpaceOnUse" gradientTransform="translate(-.09 -24.144)"/></defs><rect width="19.487" height="19.487" x="2.283" y="-21.86" transform="rotate(90)" ry="0" fill="url(#cmb)"/><path style="line-height:125%" d="M16.802 5.768l-.013.002a6.43 6.43 0 0 0-1.182.192 5.062 5.062 0 0 0-1.494.718c-.428.323-.817.72-1.17 1.191-.34.48-.682 1.032-1.022 1.66-.12.228-.233.424-.35.636v.002h-.004l-1.34 2.394-.005-.002c-.238.443-.461.847-.665 1.198a4.358 4.358 0 0 1-.716.94 2.79 2.79 0 0 1-.907.594c-.072.027-.161.042-.242.063h-.989v2.414h.989v-.002a6.427 6.427 0 0 0 1.185-.192 5.062 5.062 0 0 0 1.494-.718 5.94 5.94 0 0 0 1.171-1.191c.34-.48.681-1.033 1.021-1.66.12-.228.235-.425.353-.637l.006.002.003-.005.037-.066h2.53v.002h1.124v-2.408h-.33v-.001h-1.98c.22-.407.432-.789.621-1.115.214-.37.452-.682.717-.94a2.79 2.79 0 0 1 .906-.594c.07-.027.16-.041.239-.061h.992V8.18h-.002V5.77h-.977v-.002z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol class="cnflow-logo" viewBox="0 0 299.99999 300" id="flow" xmlns="http://www.w3.org/2000/svg"><title>Flow logo</title><path d="M38.75 33.427l77.461 77.47H54.436l61.145 61.16H38.437l93.462 93.478v-77.158l.01-.01v-77.47h-.01V66.982h46.691l20.394 20.393H153.57v76.531h22.05l24.474 24.473h-15.806l-.01-.01v.01h-31.665l-.01-.01v.01h-.313l.313.313v77.148h109.149l-39.2-39.2v-15.806l8.465 8.466v-77.37h-15.682l.017-38.191 30.09 30.086V56.362h-64.874l-22.94-22.934H113.71z" fill="#fbc02d" fill-opacity=".976" stroke-width=".955" class="cnflow-logo-mark"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="coa" x1="-388.15%" x2="237.68%" y1="-144.18%" y2="430.41%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cob" x1="72.945%" x2="-97.052%" y1="84.424%" y2="-147.7%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="coc" x1="-283.88%" x2="287.54%" y1="-693.6%" y2="101.71%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cod" x1="-821.19%" x2="101.99%" y1="-469.05%" y2="288.24%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coe" x1="-140.36%" x2="419.01%" y1="-230.93%" y2="261.98%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cof" x1="191.08%" x2="20.358%" y1="253.95%" y2="20.403%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cog" x1="-388.09%" x2="237.67%" y1="-173.85%" y2="518.99%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#coa"/><linearGradient id="coj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cob"/><linearGradient id="cok" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#coc"/><linearGradient id="col" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cod"/><linearGradient id="com" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#coe"/><linearGradient id="con" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cof"/><linearGradient id="coo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cog"/><linearGradient id="cop" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#coh"/><linearGradient id="coh" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#coi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#coj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cok)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#col)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#com)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#con)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#coo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cop)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia-open" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cpi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpa"/><linearGradient id="cpa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cpb"/><linearGradient id="cpb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#cpc"/><linearGradient id="cpc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cpd"/><linearGradient id="cpd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#cpe"/><linearGradient id="cpe" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cpf"/><linearGradient id="cpf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpg"/><linearGradient id="cpg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpp" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#cph"/><linearGradient id="cph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#cpi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#cpj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cpk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#cpl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#cpm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#cpn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#cpo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cpp)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#cddc39" fill-rule="nonzero"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#cddc39"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00acc1" fill-rule="nonzero"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00acc1"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#e57373" fill-rule="nonzero"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" stroke-width=".644"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#e57373"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" fill-rule="evenodd" stroke-width=".644"/></symbol><symbol id="folder-docker" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs id="cydefs10"><path id="cySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><path id="cypath2" d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><style id="cystyle2">.cyst0{fill:#fff}.cyst1{clip-path:url(#cySVGID_4_)}</style><g id="cyg34" transform="translate(8.319 9.626) scale(.39491)" fill="#b3e5fc"><g id="cyg32"><g id="cyg30"><title id="cytitle4">Group 3</title><g id="cyg28"><g id="cyg26"><g id="cyg9"><path id="cySVGID_1_" class="cyst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="cyg24"><clipPath id="cySVGID_4_"><use id="cyuse14" width="100%" height="100%" xlink:href="#cySVGID_2_"/></clipPath><g id="cyg22" class="cyst1" clip-path="url(#cySVGID_4_)"><g id="cyg20"><g id="cyg18"><path id="cySVGID_3_" class="cyst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docker-open" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="cza"><use width="100%" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#SVGID_2_"/></clipPath></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><g transform="matrix(.3949 0 0 .39489 8.319 9.626)" fill="#b3e5fc"><title>Group 3</title><path class="czst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/><g class="czst1" clip-path="url(#cza)"><path class="czst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#01579b" fill-rule="nonzero"/><style>.dcst0{fill:#1173b6}.st1{fill:#585d67}</style><path class="dcst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#01579b"/><path class="ddst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M21.132 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.217 1.217m.608-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M21.133 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.216 1.217m.609-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ffca28" fill-rule="nonzero"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.411 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill-rule="nonzero" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ffca28"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.412 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#c0ca33" fill-rule="nonzero"/><path d="M17.39 12.544a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#c0ca33"/><path d="M17.391 12.543a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.036 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.968 4.968 0 0 1-2.679 2.203m-.155-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.1-1.238h2.894c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399h-1.806a4.902 4.902 0 0 1 2.672-2.202c-.37.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.049.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.94 4.94 0 0 1 2.679 2.202m-4.281-3.712a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.037 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.967 4.967 0 0 1-2.68 2.203m-.154-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.099-1.238h2.895c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399H13.06a4.902 4.902 0 0 1 2.672-2.202c-.371.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.05.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.941 4.941 0 0 1 2.679 2.202M17.34 9.322a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M16.473 13.927c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a17.015 17.015 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359a5.558 5.558 0 0 0-.203.604c.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.569-1.053c-.21-.372-.435-.702-.639-1.032-.38-.022-.78-.022-1.2-.022-.422 0-.823 0-1.202.022-.204.33-.428.66-.639 1.032l-.569 1.053.57 1.054c.21.372.434.702.638 1.032.38.021.78.021 1.201.021.421 0 .822 0 1.201-.02.204-.331.428-.661.639-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.245-9.284c-.436-.267-1.405.14-2.522 1.194.366.414.724.864 1.06 1.334.577.057 1.146.14 1.686.253.359-1.503.225-2.535-.224-2.78m-.492 4.03l.204.358c.077-.203.154-.407.203-.604-.19-.042-.4-.077-.618-.112l.211.358m1.018-4.95c1.033.589 1.145 2.141.71 3.953 1.784.527 3.069 1.398 3.069 2.584 0 1.187-1.285 2.058-3.07 2.585.436 1.812.324 3.364-.709 3.954-1.025.59-2.423-.085-3.77-1.37-1.35 1.285-2.747 1.96-3.78 1.37-1.025-.59-1.137-2.142-.702-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.203 6.538c.239.527.45 1.054.625 1.588 1.475-.443 2.303-1.075 2.303-1.588 0-.512-.828-1.144-2.303-1.587a15.81 15.81 0 0 1-.625 1.587m-7.136 0a15.806 15.806 0 0 1-.625-1.587c-1.474.443-2.303 1.075-2.303 1.587 0 .513.829 1.145 2.303 1.588.176-.534.387-1.06.625-1.588m6.321 1.588l-.21.358c.217-.035.428-.07.617-.113-.049-.196-.126-.4-.203-.604l-.204.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.522 1.194.45-.246.583-1.278.224-2.781-.54.112-1.11.196-1.685.253-.337.47-.695.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.113.049.196.126.4.203.604l.204-.359m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M16.473 13.928c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a16.997 16.997 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359c-.077.204-.154.408-.203.604.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.57-1.053c-.21-.372-.434-.702-.638-1.032-.38-.022-.78-.022-1.201-.022-.421 0-.822 0-1.2.022-.205.33-.43.66-.64 1.032l-.569 1.053.569 1.054c.21.372.435.702.64 1.032.378.021.779.021 1.2.021.421 0 .822 0 1.2-.02.205-.33.43-.661.64-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.244-9.284c-.435-.267-1.404.14-2.52 1.194.364.414.723.864 1.06 1.334.575.057 1.144.14 1.685.253.358-1.503.225-2.535-.225-2.78m-.491 4.03l.203.358c.078-.203.155-.407.204-.604-.19-.042-.4-.077-.618-.112l.21.358m1.02-4.95c1.032.589 1.144 2.141.708 3.953 1.784.527 3.07 1.398 3.07 2.584 0 1.187-1.286 2.058-3.07 2.585.436 1.812.323 3.364-.709 3.954-1.025.59-2.423-.085-3.771-1.37-1.348 1.285-2.746 1.96-3.778 1.37-1.026-.59-1.138-2.142-.703-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.204 6.538c.24.527.45 1.054.625 1.588 1.475-.443 2.304-1.075 2.304-1.588 0-.512-.829-1.144-2.304-1.587a15.81 15.81 0 0 1-.625 1.587m-7.135 0a15.808 15.808 0 0 1-.625-1.587c-1.475.443-2.303 1.075-2.303 1.587 0 .513.828 1.145 2.303 1.588.176-.534.386-1.06.625-1.588m6.32 1.588l-.21.358c.218-.035.428-.07.618-.113a5.56 5.56 0 0 0-.204-.604l-.203.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.521 1.194.45-.246.583-1.278.225-2.781-.54.112-1.11.196-1.685.253-.338.47-.696.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.112.049.197.126.4.203.604l.204-.358m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#fbc02d" fill-rule="nonzero"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.434h-1.217v8.518a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#fbc02d"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.433h-1.217v8.519a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#4caf50" fill-rule="nonzero"/><g fill="#c8e6c9" transform="translate(2.065 -.225) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#4caf50"/><g fill="#c8e6c9" fill-rule="evenodd" transform="translate(2.064 -.224) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1e88e5" fill-rule="nonzero"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1e88e5"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.811 8.52l-5.988 5.506-3.346-2.522-1.383.805 3.298 3.03-3.298 3.032 1.383.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.622v6.396l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.81 8.52l-5.988 5.506-3.346-2.522-1.384.805 3.3 3.03-3.3 3.032 1.384.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.621v6.397l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><g transform="translate(8.459 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688"/><g transform="translate(8.458 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#03a9f4" fill-rule="nonzero"/><g transform="translate(9.192 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#03a9f4"/><g transform="translate(9.193 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol viewBox="0 0 24 24" id="font" xmlns="http://www.w3.org/2000/svg"><path d="M9.62 12L12 5.67 14.37 12M11 3L5.5 17h2.25l1.12-3h6.25l1.13 3h2.25L13 3h-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 500 500" id="fsharp" xmlns="http://www.w3.org/2000/svg"><path d="M235.906 36.66L21.963 250.601l213.943 213.943v-84.36L106.209 250.487l129.697-129.696z" fill="#378bba" stroke-width="14.706"/><path d="M235.906 156.614l-93.622 93.62 93.622 93.622z" fill="#378bba" stroke-width="15.006"/><path d="M263.417 36.64L477.36 250.583 263.417 464.526v-84.36l129.696-129.697-129.696-129.696z" fill="#30b9db" stroke-width="14.706"/></symbol><symbol viewBox="0 0 152.99 160.01" id="fusebox" xmlns="http://www.w3.org/2000/svg"><defs id="fkdefs4"><style id="fkstyle2">.fkcls-1{fill:#fff}.fkcls-2{fill:#515151}.fkcls-3{fill:#1d79bf}.fkcls-4{fill:#383838}</style></defs><title id="fktitle6">Asset 3</title><g id="fkLayer_2" data-name="Layer 2" transform="matrix(.87285 0 0 .87285 10.17 10.175)"><g id="fkFuse_Box" data-name="Fuse Box"><g id="fkLOGO"><path class="fkcls-1" id="fkpolygon8" fill="#fff" d="M76.56 2.19l74.22 24.93-7.7 87.77-65.41 42.66-69.79-43.93-5.7-86.13z"/><path class="fkcls-2" d="M77.69 160L5.87 114.81 0 26 76.55 0 153 25.67l-7.94 90.4zM9.88 112.43l67.77 42.66 63.45-41.39 7.47-85.13-72-24.18L4.36 28.95z" id="fkpath10" fill="#515151"/><path class="fkcls-3" id="fkpolygon12" fill="#1d79bf" d="M76.4 148.8V61.68l66.93-29.82-5.99 78.77z"/><path id="fkF" class="fkcls-4" fill="#383838" d="M76.4 148.8l-60.35-37.39L9.63 31.8 76.4 61.68z"/><path class="fkcls-1" d="M25.58 52.73l.54 15.93 37.35 18.18.12 14.69-37-18.21 1.64 37.1-14.56-9-5.05-80.55 67.79 30.82v15.46z" id="fkpath15" fill="#fff"/><path class="fkcls-1" d="M135.91 90.77c-.08 13.12-6.33 26.59-16.77 33.12l-42.8 27.93V61.71l42.27-18.84c5.16-2.41 9.51-1.43 12.4 3.11 1.9 3 2.89 7.23 2.86 12.21A35.69 35.69 0 0 1 129.34 76c4.29 2 6.66 6.55 6.57 14.77zM123 63.76c0-4.64-2-6.93-4.92-5.45l-29 14.48L89 90l29.44-15.59c2.5-1.32 4.56-5.91 4.56-10.65zM125.15 96c0-5.71-2.42-8.24-6.55-5.93L89 106.64v19.58l29.34-17.46c4.43-2.64 6.79-7.27 6.81-12.76z" id="fkpath17" fill="#fff"/><path id="fkTOP" class="fkcls-4" fill="#383838" d="M76.4 8.82L9.71 31.77l109.77 2.38-84.02 9.21L76.4 61.68l20.76-9.25-27.73-1.37 49.78-8.46 24.12-10.74z"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="git" xmlns="http://www.w3.org/2000/svg"><path d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2 2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82z" fill="#e64a19"/></symbol><symbol viewBox="0 0 494 455" id="gitlab" xmlns="http://www.w3.org/2000/svg"><title>logo</title><defs><path id="fma" d="M0 1173.3h2000V0H0v1173.3z"/></defs><g transform="matrix(.88256 0 0 -.88256 -286.767 742.766)" fill="none" fill-rule="evenodd"><mask id="fmb" fill="#fff"><use width="100%" height="100%" xlink:href="#fma"/></mask><g><g transform="translate(358.67 358.67)"><path d="M492.532 195.445l-27.559 84.815-54.617 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.619-168.1h-181.37l-54.62 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.617-168.1-27.557-84.815a18.775 18.775 0 0 1 6.82-20.992l238.51-173.29 238.51 173.29a18.777 18.777 0 0 1 6.82 20.992" fill="#fc6d26"/><path d="M247.2 1.16l90.684 279.1h-181.37z" fill="#e24329"/><path d="M247.201 1.16l-90.684 279.09H29.427z" fill="#fc6d26"/><path d="M29.422 280.256L1.862 195.44a18.774 18.774 0 0 1 6.822-20.991L247.194 1.16z" fill="#fca326"/><path d="M29.422 280.26h127.09l-54.619 168.1c-2.81 8.65-15.047 8.65-17.856 0z" fill="#e24329"/><path d="M247.2 1.16l90.684 279.09h127.09z" fill="#fc6d26"/><path d="M464.98 280.256l27.559-84.815a18.774 18.774 0 0 0-6.821-20.991L247.208 1.16z" fill="#fca326"/><path d="M464.97 280.26H337.88l54.619 168.1c2.81 8.65 15.047 8.65 17.856 0z" fill="#e24329"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="go" xmlns="http://www.w3.org/2000/svg"><path d="M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.123 5.502 4.757 5.502h2.286c2.634 0 4.757-2.453 4.757-5.502v-4.6a5.1 5.1 0 0 0 .026.003v-4.71c0-3.049-2.122-5.502-4.756-5.502h-2.287z" fill="#73cddc"/><rect width="2.289" height="3.335" x="-1.178" y="6.092" ry="1.125" transform="matrix(.4849 -.87457 .85979 .51065 0 0)" fill="#73cddc"/><rect width="2.297" height="3.39" x="10.261" y="-15.076" ry="1.143" transform="matrix(.44646 .8948 -.89204 .45195 0 0)" fill="#73cddc"/><circle cx="9.267" cy="5.13" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><circle cx="14.214" cy="5.116" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><ellipse cx="8.039" cy="5.051" rx=".792" ry=".901" fill="#030d18"/><path d="M11.792 9.556l.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.402.69.403.689 0 0 1-.404-.69.403.689 0 0 1 .035-.276z" fill="#fff" stroke="#fff" stroke-width=".155"/><ellipse cx="8.51" cy="5.365" rx=".138" ry=".166" fill="#fff"/><ellipse cx="12.945" cy="5.189" rx=".792" ry=".901" fill="#030d18"/><ellipse cx="13.414" cy="5.446" rx=".138" ry=".166" fill="#fff"/><ellipse cx="-12.982" cy="-3.409" rx=".708" ry="1.026" transform="rotate(-129.403)" fill="#f6d2a1" stroke-width=".4"/><path d="M11.772 9.553l-.757.135a.4.672 0 0 0-.008.135.4.672 0 0 0 .4.672.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z" fill="#fff" stroke="#fff" stroke-width=".153"/><ellipse cx="1.841" cy="-21.563" rx=".707" ry="1.026" transform="scale(1 -1) rotate(50.597)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="-17.281" cy="-21.784" rx=".864" ry="1.27" transform="matrix(.3054 -.95222 -.97065 -.2405 0 0)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="22.885" cy="2.587" rx=".864" ry="1.27" transform="matrix(.22652 .974 .95652 -.29167 0 0)" fill="#f6d2a1" stroke-width=".4"/><path d="M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.264.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .594-.267.594-.598V8.99a.595.595 0 0 0-.594-.597h-2.115z" fill="#f6d2a1" stroke="#657075" stroke-width=".1"/><ellipse cx="11.734" cy="8.203" rx="1.208" ry=".68" fill="#030d18" stroke="#fff" stroke-width=".162"/></symbol><symbol viewBox="0 0 24 24" id="gradle" xmlns="http://www.w3.org/2000/svg"><path d="M21.718 5.503c-.731-1.315-2.04-1.708-2.963-1.727-1.133-.023-2.065.605-1.888 1.017.037.088.25.55.38.741.19.275.527.064.646 0 .353-.187.73-.248 1.16-.198.409.048.954.3 1.319 1.001.859 1.652-1.794 5.05-5.114 2.697-3.32-2.353-6.548-1.574-8.01-1.1-1.462.475-2.135.952-1.556 2.055.785 1.498.524 1.038 1.285 2.28 1.21 1.97 3.856-.908 3.856-.908-1.972 2.906-3.662 2.204-4.31 1.188a15.864 15.864 0 0 1-1.038-1.97c-4.993 1.76-3.642 9.534-3.642 9.534h2.48c.632-2.862 2.892-2.757 3.28 0h1.892c1.673-5.59 5.914 0 5.914 0h2.466c-.69-3.812 1.388-5.01 2.697-7.246 1.31-2.235 2.551-4.969 1.146-7.364zm-6.362 7.362c-1.304-.426-.837-1.723-.837-1.723s1.139.368 2.68.87c-.09.403-.856 1.175-1.843.853z" fill="#0097a7" stroke-width=".47"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 300 300" id="graphcool" xmlns="http://www.w3.org/2000/svg"><path d="M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768 9.335-3.478 14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.313 17.313 0 0 1-8.635 14.95l-75.739 43.755a17.312 17.312 0 0 1-17.261 0l-75.74-43.756a17.312 17.312 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794c-3.397-9.327-12.962-14.904-22.751-13.27-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.932 31.932 0 0 0-15.978 27.588v87.454a31.933 31.933 0 0 0 15.927 27.602l75.74 43.755a31.934 31.934 0 0 0 31.846 0l75.74-43.755a31.933 31.933 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z" fill="#27ae60" stroke="#27ae60" stroke-width="7.883622079999999"/></symbol><symbol viewBox="0 0 400 400" id="graphql" xmlns="http://www.w3.org/2000/svg"><path d="M67.008 293.022l-13.143-7.588L200.282 31.839l13.143 7.588z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M50.855 265.174H343.69v15.177H50.855z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M203.122 358.269L56.649 273.7l7.589-13.143 146.472 84.568zm127.24-220.407L183.889 53.293l7.589-13.143 146.472 84.568z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M64.278 137.803l-7.588-13.142 146.472-84.568 7.588 13.143z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M327.661 293.025L181.244 39.43l13.143-7.589 146.417 253.596zM62.466 114.597h15.176v169.136H62.466zm254.528 0h15.176v169.136h-15.176z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M200.538 351.845l-6.628-11.481L321.3 266.812l6.629 11.48z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M352.284 288.67c-8.777 15.268-28.342 20.48-43.61 11.703-15.268-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.36 8.869 20.57 28.342 11.703 43.61M97.574 141.567c-8.778 15.268-28.343 20.48-43.61 11.703-15.269-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.268 8.869 20.479 28.342 11.702 43.61M42.353 288.67c-8.777-15.268-3.566-34.741 11.702-43.61 15.268-8.776 34.741-3.565 43.61 11.703 8.776 15.268 3.565 34.741-11.703 43.61-15.36 8.776-34.833 3.565-43.61-11.703m254.71-147.103c-8.776-15.268-3.565-34.741 11.703-43.61 15.268-8.776 34.742-3.565 43.61 11.703 8.777 15.268 3.566 34.741-11.702 43.61-15.268 8.776-34.833 3.565-43.61-11.703m-99.745 236.608c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907c0 17.554-14.262 31.907-31.907 31.907m0-294.206c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907-14.262 31.907-31.907 31.907" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/></symbol><symbol viewBox="0 0 24 24" id="groovy" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.982a10.119 10.119 0 0 0-10.12 10.12A10.119 10.119 0 0 0 12 22.22 10.119 10.119 0 0 0 22.12 12.1 10.119 10.119 0 0 0 12 1.983zm1.254 2.422c.91 0 1.647.261 2.213.78.571.518.857 1.188.857 2.013 0 .889-.319 1.673-.959 2.35-.64.677-1.376 1.015-2.207 1.015-.486 0-.89-.119-1.213-.357-.317-.238-.476-.532-.476-.88 0-.212.06-.4.181-.563.127-.164.274-.246.438-.246.159 0 .238.092.238.277 0 .164.06.29.182.38.121.09.261.136.42.136.423 0 .828-.29 1.215-.866.391-.582.587-1.202.587-1.863 0-.465-.151-.844-.453-1.135-.301-.296-.69-.445-1.166-.445-.714 0-1.406.318-2.078.953-.666.635-1.211 1.47-1.635 2.506-.417 1.031-.627 2.014-.627 2.945 0 .857.185 1.54.555 2.047.37.503.863.754 1.477.754 1.037 0 2.027-.734 2.974-2.2l1.493-.212c.185-.026.277.018.277.135 0 .053-.072.28-.215.681-.143.402-.337 1.074-.586 2.016.82-.476 1.455-1.003 1.904-1.58v.914c-.36.418-1.046.888-2.062 1.412-.212 1.407-.682 2.493-1.406 3.26-.725.772-1.54 1.16-2.444 1.16-.433 0-.775-.102-1.023-.303-.243-.2-.365-.477-.365-.832 0-.984.955-1.94 2.865-2.865.2-.714.395-1.356.586-1.928-.333.482-.817.907-1.451 1.278-.635.37-1.225.554-1.77.554-.889 0-1.628-.383-2.22-1.15-.588-.772-.881-1.748-.881-2.928 0-1.243.333-2.42 1-3.531a7.747 7.747 0 0 1 2.625-2.674c1.084-.672 2.134-1.008 3.15-1.008zM12.03 16.592c-1.375.687-2.062 1.365-2.062 2.031 0 .354.169.533.508.533.666 0 1.184-.856 1.554-2.564z" fill="#26c6da"/></symbol><symbol viewBox="0 0 24 24" id="gulp" xmlns="http://www.w3.org/2000/svg"><path d="M8.37 15.94a596.238 596.238 0 0 1-.482-4.982c.002-.042-.225-.077-.505-.077h-.508V8.95h3.966V5.198l1.871-1.124c1.14-.685 1.978-1.125 2.144-1.125.4 0 .866.506.866.939 0 .19-.057.422-.127.517-.07.095-.722.53-1.45.966l-1.321.792-.029 1.393-.028 1.393h3.972v1.932h-.98l-.495 4.983-.495 4.983H8.854l-.485-4.906z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="h" xmlns="http://www.w3.org/2000/svg"><path d="M16.745 19.818h-3.007v-5.882q0-2.381-1.736-2.381-.869 0-1.438.663-.56.662-.56 1.718v5.882H6.988V4.533h3.016v6.508h.037q1.186-1.802 3.193-1.802 3.511 0 3.511 4.239z" stroke-width=".478" fill="#0277bd"/></symbol><symbol viewBox="0 0 253.6 253.6" id="hack" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-29.243 -29.515) scale(1.2301)"><path fill="#607d8b" d="M69.496 159.551v52.576l51.77-52.576zM123.507 41.523l-54.01 52.755v55.084l54.01-54.009z"/><path fill="#eceff1" d="M130.023 95.663v51.501l52.128-51.5z"/><path fill="#607d8b" d="M185.465 101.867l-55.442 55.174v55.083l55.442-55.262z"/><path fill="#ffa000" d="M73.068 154.283l50.427.09v-50.248z"/></g></symbol><symbol viewBox="0 0 300 300.00001" id="haml" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 165.6)"><path d="M78.42-132.307c-12.047-.302-26.924 5.998-26.924 5.998l49.195 99.791L74.605 85.005c23.81 20.134 50.07 10.504 50.07 10.504L136.76 9.212c1.526 1.446 3.146 2.77 4.777 3.995 5.244 3.714 10.925 6.553 16.606 8.738 5.68 2.185 11.583 3.933 17.482 5.244 3.933.874 7.645 1.53 11.578 1.967-1.748 3.933-2.84 8.083-2.621 12.672 0 .437.22.873.656 1.092h.217c4.152 2.185 8.521 3.934 13.328 5.027 4.589.874 9.615 1.312 14.422.656 5.026-.655 10.051-2.623 13.984-5.9 3.933-3.278 6.774-7.648 8.522-12.237l.219-.218v-.217l.656-5.899v-.22c2.185-1.311 4.37-2.621 6.555-4.37 2.622-2.184 5.025-4.589 6.773-7.648 1.748-3.059 2.84-6.774 2.621-10.488-.218-3.496-1.53-6.99-3.06-10.049-1.53-3.059-3.495-5.901-5.68-8.523-4.37-5.026-9.614-9.176-15.295-12.454-5.462-3.496-11.581-6.338-17.7-8.304l-2.404-.656-1.962-.655c-1.311-.437-2.406-1.092-3.498-1.53-2.185-1.31-3.717-2.622-4.809-4.37-2.185-3.278-2.403-8.301-1.31-13.545.218-1.311.656-2.623 1.093-3.934a96.064 96.064 0 0 0 1.31-4.152c.314-1.412.51-2.829.598-4.402l29.203-25.553c-2.275-8.404-27.488-17.158-27.488-17.158l-74.931 63.726-43.243-81.584c-1.553-.35-3.218-.527-4.94-.57zm107.682 73.14c-.449 2.336-.647 4.795-.647 7.258.219 3.715 1.311 7.87 3.715 11.366 2.403 3.496 5.68 6.117 8.957 7.646a29.663 29.663 0 0 0 5.027 1.967l2.623.654 2.184.438c5.68 1.53 11.142 3.714 16.168 6.554 5.025 2.84 9.833 6.337 13.766 10.27s6.992 8.959 7.43 13.984c.218 3.496-.22 6.118-1.313 8.303-1.093 2.404-2.84 4.588-4.807 6.555-.874.874-1.966 1.747-2.84 2.402a27.11 27.11 0 0 0-.654-5.898c-.219-1.093-.438-1.966-.875-3.059-.437-.874-.872-1.966-1.965-2.621-.218 0-.44-.001-.44.217-1.31 3.277-3.494 6.12-5.898 8.086-2.403 1.966-5.462 2.84-8.521 3.058-3.06.219-6.338-.436-9.616-1.31-3.277-.874-6.552-1.968-9.83-3.06l-.439-.22c-.656-.218-1.526.002-1.963.44-1.748 2.185-3.06 4.149-4.59 6.334a58.435 58.435 0 0 0-2.84 5.027c-3.933-1.53-7.649-2.841-11.582-4.37-5.462-2.186-10.925-4.37-15.95-6.991-5.245-2.404-10.268-5.246-14.638-8.524-3.15-2.363-6.062-4.845-8.185-7.681l2.404-17.172z" fill="#f4511e" stroke-width="0" stroke-linejoin="round"/></g></symbol><symbol viewBox="0 0 24 24" id="handlebars" xmlns="http://www.w3.org/2000/svg"><path d="M8.55 10.32c-2.753 0-4.202 3.48-5.793 3.48-.98 0-1.126-.677-1.126-.915 0-.332.236-.706.564-.706.59 0 .414.77.414.77s.798-.555.272-1.298c-.42-.595-1.31-.623-1.92-.17-.617.458-1.057 1.146-.853 2.287.1.551.468 1.35 1.233 1.805.764.455 1.925.566 2.335.566 2.194 0 4.342-1.633 6.639-2.322a5.513 5.513 0 0 1 1.497-.222 6.19 6.19 0 0 1 1.92.226c2.296.689 4.444 2.323 6.638 2.323.41 0 1.57-.11 2.335-.566.765-.455 1.132-1.256 1.231-1.807.204-1.14-.235-1.829-.853-2.287-.61-.453-1.497-.423-1.918.172-.526.743.27 1.297.27 1.297s-.176-.77.414-.77c.329 0 .565.373.565.705 0 .238-.147.914-1.126.914-1.592 0-3.04-3.478-5.794-3.478-2.565 0-3.076 1.177-3.462 1.718-.004.005-.005.011-.008.016-.005-.006-.007-.013-.012-.02-.386-.54-.896-1.717-3.461-1.717z" fill="#ff7043" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 300.00001 300" id="haskell" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2.422"><path d="M23.928 240.5l59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z" fill="#ef5350"/><path d="M83.869 240.5l59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z" fill="#ffa726"/><path d="M228.72 188.08l-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924l-19.98-29.953h99.901v29.953z" fill="#ffee58"/></g></symbol><symbol viewBox="0 0 210 210" id="haxe" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -87)"><path fill="#f68712" stroke-width=".221" d="M42.78 191.545l63.431-63.43 63.431 63.43-63.431 63.431z"/><path d="M42.8 191.592L31.193 148.28 19.59 104.97 62.9 116.575l43.311 11.605-31.706 31.706z" fill="#fab20b" stroke-width=".266"/><path d="M105.956 128.111l-43.19-11.544-43.177-11.597 22.927.185 23.228.294 20.264 11.36z" fill="#fbc707" stroke-width=".265"/><path d="M19.59 104.97l11.596 43.176 11.545 43.19-11.303-19.948-11.36-20.263-.294-23.228z" fill="#fff200" stroke-width=".265"/><path d="M106.23 128.133l43.312-11.605 43.311-11.605-11.605 43.31-11.605 43.312-31.706-31.706z" fill="#f47216" stroke-width=".266"/><path d="M169.711 191.289l11.545-43.19 11.597-43.176-.185 22.927-.294 23.228-11.36 20.263z" fill="#f1471d" stroke-width=".265"/><path d="M192.853 104.923l-43.176 11.597-43.19 11.544 19.947-11.303 20.264-11.36 23.228-.293z" fill="#fbc707" stroke-width=".265"/><path d="M169.643 191.545l11.605 43.31 11.605 43.312-43.311-11.605-43.311-11.606 31.706-31.705z" fill="#f25c19" stroke-width=".266"/><path d="M106.487 255.025l43.19 11.544 43.176 11.598-22.927-.185-23.228-.294-20.264-11.36z" fill="#f68712" stroke-width=".265"/><path d="M192.853 278.167l-11.597-43.176-11.545-43.19 11.303 19.947 11.36 20.264.294 23.228z" fill="#f1471d" stroke-width=".265"/><path d="M106.211 254.976l-43.31 11.605-43.312 11.605 11.605-43.31L42.8 191.563l31.706 31.706z" fill="#f89c0e" stroke-width=".266"/><path d="M42.731 191.82l-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.263z" fill="#fff200" stroke-width=".265"/><path d="M19.59 278.186l43.175-11.597 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z" fill="#f25c19" stroke-width=".265"/></g></symbol><symbol viewBox="0 0 144 152" id="heroku" xmlns="http://www.w3.org/2000/svg"><path d="M118.68 13.279H26.865c-6.337 0-11.476 5.139-11.476 11.476V129.32c0 6.338 5.139 11.477 11.476 11.477h91.813c6.338 0 11.477-5.14 11.477-11.477V24.755c0-6.337-5.139-11.476-11.477-11.476zM44.08 121.669V96.165l14.346 12.752zm44.632 0v-38.08c-.063-2.976-1.496-6.551-7.97-6.551-12.966 0-27.51 6.52-27.654 6.586l-9.008 4.08V32.407h12.752v36.201c6.366-2.072 15.266-4.321 23.91-4.321 7.882 0 12.6 3.099 15.17 5.698 5.484 5.547 5.56 12.613 5.551 13.43v38.255zm3.188-68.54H79.149c5.011-6.576 8.158-13.496 9.564-20.723h12.751c-.86 7.243-3.796 14.187-9.563 20.722z" fill="#6963b9"/></symbol><symbol viewBox="0 0 24 24" id="hpp" xmlns="http://www.w3.org/2000/svg"><path d="M9.757 19.818H6.751v-5.882q0-2.381-1.737-2.381-.868 0-1.438.663-.56.662-.56 1.718v5.882H0V4.533h3.016v6.508h.037Q4.24 9.239 6.247 9.239q3.51 0 3.51 4.239z" stroke-width=".478" fill="#0277bd"/><path d="M13.073 11.448v2h-2v2h2v2h2v-2h2v-2h-2v-2zm7 0v2h-2v2h2v2h2v-2h2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="html" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.56l4.07-1.13.55-6.1H9.38L9.2 8.3h7.6l.2-1.99H7l.56 6.01h6.89l-.23 2.58-2.22.6-2.22-.6-.14-1.66h-2l.29 3.19L12 17.56M4.07 3h15.86L18.5 19.2 12 21l-6.5-1.8L4.07 3z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" id="http" xmlns="http://www.w3.org/2000/svg"><path d="M16.046 13.784c.074-.613.13-1.225.13-1.856s-.056-1.244-.13-1.856h3.137c.148.594.241 1.215.241 1.856a7.65 7.65 0 0 1-.241 1.856m-4.78 5.16c.557-1.03.984-2.144 1.281-3.304h2.738a7.452 7.452 0 0 1-4.019 3.304m-.232-5.16H9.828a12.314 12.314 0 0 1-.149-1.856c0-.631.056-1.253.149-1.856h4.343c.084.603.149 1.225.149 1.856 0 .63-.065 1.243-.149 1.856M12 19.315c-.77-1.113-1.393-2.348-1.773-3.675h3.545c-.38 1.327-1.002 2.562-1.773 3.675m-3.712-11.1h-2.71a7.353 7.353 0 0 1 4.01-3.304c-.557 1.03-.975 2.144-1.3 3.304m-2.71 7.425h2.71c.325 1.16.743 2.274 1.3 3.304a7.433 7.433 0 0 1-4.01-3.304m-.761-1.856a7.65 7.65 0 0 1-.241-1.856c0-.64.093-1.262.241-1.856h3.137c-.074.612-.13 1.225-.13 1.856 0 .63.056 1.243.13 1.856m4.046-9.253c.77 1.114 1.393 2.357 1.773 3.684h-3.545c.38-1.327 1.002-2.57 1.773-3.684m6.422 3.684h-2.738a14.523 14.523 0 0 0-1.28-3.304 7.412 7.412 0 0 1 4.018 3.304m-6.423-5.568c-5.132 0-9.28 4.176-9.28 9.28a9.28 9.28 0 0 0 9.28 9.282 9.28 9.28 0 0 0 9.281-9.281A9.28 9.28 0 0 0 12 2.647z" fill="#e53935" stroke-width=".928"/></symbol><symbol viewBox="0 0 24 24" id="image" xmlns="http://www.w3.org/2000/svg"><path d="M13.009 9.202h5.368l-5.368-5.368v5.368M6.177 2.37h7.808l5.856 5.856v11.711a1.952 1.952 0 0 1-1.952 1.952H6.178a1.951 1.951 0 0 1-1.952-1.952V4.322c0-1.083.868-1.952 1.952-1.952m0 17.567h11.71V12.13l-3.903 3.903-1.952-1.951-5.856 5.855M8.13 9.202a1.952 1.952 0 0 0-1.952 1.952 1.952 1.952 0 0 0 1.952 1.952 1.952 1.952 0 0 0 1.952-1.952A1.952 1.952 0 0 0 8.13 9.202z" fill="#26a69a" stroke-width=".976"/></symbol><symbol viewBox="0 0 512 512" id="ionic" xmlns="http://www.w3.org/2000/svg"><g fill="#4f8ff7"><path d="M423.592 132.804A31.855 31.855 0 0 0 429 115c0-17.675-14.33-32-32-32a31.853 31.853 0 0 0-17.805 5.409C344.709 63.015 302.11 48 256 48 141.125 48 48 141.125 48 256c0 114.877 93.125 208 208 208 114.873 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.196zM391.83 391.832c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089-25.94 0-51.095-5.077-74.767-15.089-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064C68.982 307.096 63.905 281.94 63.905 256c0-25.94 5.077-51.095 15.089-74.767 9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09 25.939 0 51.094 5.077 74.766 15.089a191.221 191.221 0 0 1 37.802 21.327A31.853 31.853 0 0 0 365 115c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191.085 191.085 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767 0 25.939-5.077 51.096-15.09 74.768-9.675 22.873-23.527 43.418-41.175 61.064z"/><circle cx="256.003" cy="256" r="96"/></g></symbol><symbol viewBox="0 0 24 24" id="java" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="javascript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="javascript-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#ffca28"/><path d="M2.444 2.506h14.135v14.136H2.444V2.506m3.714 11.811c.315.668.935 1.218 1.995 1.218 1.178 0 1.987-.629 1.987-2.003V8.993H8.805v4.508c0 .675-.275.848-.707.848-.455 0-.644-.314-.856-.683l-1.084.651m4.697-.14c.392.769 1.185 1.358 2.426 1.358 1.257 0 2.199-.652 2.199-1.854 0-1.107-.636-1.602-1.767-2.089l-.33-.141c-.573-.243-.816-.408-.816-.801 0-.322.243-.573.636-.573.377 0 .628.165.856.573l1.028-.683c-.432-.754-1.044-1.045-1.884-1.045-1.186 0-1.948.754-1.948 1.752 0 1.083.636 1.594 1.594 2.002l.33.141c.613.267.974.432.974.888 0 .377-.354.652-.903.652-.652 0-1.029-.338-1.312-.81l-1.083.63z" fill="#ffca28"/></symbol><symbol viewBox="0 0 180 180" id="jenkins" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="gia"><path transform="scale(1 -1)" fill="#37474f" d="M.899-144.42h144.42V0H.899z"/></clipPath></defs><g transform="matrix(1.0691 0 0 -1.0691 9.4 166.143)" clip-path="url(#gia)"><g fill-rule="evenodd"><path d="M107.96 30.661l-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066" fill="#f0d6b7"/><path d="M97.334-23.425l-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876" fill="#335061"/><path d="M23.238 85.687l8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005 1.251-10.63" fill="#6d6b6d"/><path d="M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253 7.19.626M26.676 87.875s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63 3.439 2.188" fill="#dcd9d8"/></g><g fill="#f7e4cd"><path d="M36.681 58.799l4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042M94.209 64.739s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468" fill-rule="evenodd"/><path d="M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625M82.327 99.129s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19"/><g fill-rule="evenodd"><path d="M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751.312-4.69M45.435 98.504s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382M117.97 122.27s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751"/><path d="M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404"/></g></g><g fill="#49728b" fill-rule="evenodd"><path d="M48.874 26.597L19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868"/></g><g fill-rule="evenodd"><path d="M100.15-23.739l11.567.313 2.814-28.764-11.881-1.563-2.5 30.014" fill="#335061"/><path d="M103.27-23.739l17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148l-6.565-26.887" fill="#335061"/><path d="M111.09-21.55l-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566" fill="#49728b"/><path d="M62.85-85.332l-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876-14.382 3.126" fill="#fff"/><path d="M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944" fill="#dcd9d8"/><path d="M115.06-49.691l12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56" fill="#fff"/><path d="M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25" fill="#dcd9d8"/></g><path d="M102.56-21.241L95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134" fill="#fff"/><path d="M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814 2.19-.938 6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313" fill="#dcd9d8" fill-rule="evenodd"/><path d="M65.664 25.968l-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627" fill="#fff"/><path d="M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752.938 1.251" fill="#dcd9d8" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43" fill="#d33833" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669" fill="#d33833" fill-rule="evenodd"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695" fill="#d33833" fill-rule="evenodd"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M102.87 10.649s-2.19 3.127-.626 4.065c1.564.938 3.127 0 4.065 1.563s0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93" fill="#ef3d3a" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99M119.79 101.4l.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626M82.63 101.29c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768M118.26 67.253c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63M37.668 71.387c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723M122.15 63.257c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091M82.149 59.745c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37M81.136 54.523c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645"/></g><path d="M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43" fill="#81b0c4" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37M143.21-30.661c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726M144.58-37.19c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346"/></g><g fill-rule="evenodd"><path d="M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808" fill="#dcd9d8"/><path d="M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378" fill="#f0d6b7"/><path d="M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423" fill="#81b0c4"/><path d="M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808zm-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752zm16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586zm25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035zM99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431zm-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11zm-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695zm-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123.04 1.011.078 2.021.119 3.032-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989.016.405.033.81.047 1.21-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a355.817 355.817 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505zm63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44zM27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576" fill="#231f20"/><path d="M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712" fill="#f7e4cd"/><path d="M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001M102.81-4.355a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0" fill="#1d1919"/></g><g><rect transform="matrix(.8 0 0 -.8 0 144)" x="16.854" y="177.38" width="70.412" height="4.12" rx=".983" ry=".983"/><rect transform="scale(1 -1)" x="78.502" y="-2.097" width="50.037" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="13.483" y="-3.697" width="54.831" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="83.296" y="-3.697" width="45.243" height="3.296" rx=".786" ry=".786"/></g></g></symbol><symbol viewBox="0 0 24 24" id="json" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#fbc02d"/></symbol><symbol viewBox="0 0 50 50" id="julia" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" stroke-width="5.673"><circle cx="13.497" cy="281.632" r="9.555" fill="#bc342d"/><circle cx="36.081" cy="281.632" r="9.555" fill="#864e9f"/><circle cx="24.722" cy="262.389" r="9.555" fill="#328a22"/></g></symbol><symbol viewBox="0 0 64 64" id="karma" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -233)"><path d="M38.556 288.413l-20.29-26.687 9.532-7.246 20.29 26.686h-.001.002l5.527 7.247z" fill="#359b8b" stroke-width=".173"/><path d="M35.681 241.172L24.92 255.327v-14.13H12.947v13.817l7.84 33.235h4.132v-13.147l.003.003 20.29-26.686-.008-.006 5.504-7.24H35.84v.12z" fill="#3cbeae" stroke-width=".206"/></g></symbol><symbol viewBox="0 0 24 24" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7 14a2 2 0 0 1-2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2m5.65-4A5.99 5.99 0 0 0 7 6a6 6 0 0 0-6 6 6 6 0 0 0 6 6 5.99 5.99 0 0 0 5.65-4H17v4h4v-4h2v-4H12.65z" fill="#26a69a"/></symbol><symbol viewBox="0 0 24 24" id="kivy" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.89 0 0 1.89 -12.157 -11.429)" fill="#90a4ae"><path d="M7.026 8.63v4.474l1.928-1.928a.437.437 0 0 0 0-.619zM9.38 16.072v-4.474l-1.927 1.927a.437.437 0 0 0 0 .62zM18.576 10.412l-5.346.564-.017.018 2.39 2.39zM9.922 8.502s.023 3.304-.003 4.452c-.02.856.371 1.114.746 1.507.538.564 1.599 1.57 1.599 1.57a.53.53 0 0 0 .75 0l1.843-1.844a.53.53 0 0 0 0-.75z"/></g></symbol><symbol viewBox="0 0 24 24" id="kl" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:#3aaae1}.b{fill:#fdfeff}</style></defs><title>kl</title><path d="M12.033 1.737c-.25-.003-.5.11-.729.337C8.225 5.15 5.15 8.227 2.078 11.31c-.144.144-.229.346-.341.521v.41c.16.223.294.474.485.666a3259.51 3259.51 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.428 3.046-3.041 6.09-6.083 9.133-9.127.47-.47.472-1.005.006-1.472l-9.218-9.217c-.23-.23-.48-.347-.731-.35zm-1.062 4.545l1.386.832c.702.422 1.403.846 2.109 1.262a.544.544 0 0 1 .04.026l.016.013.017.013c.061.056.089.123.088.224a510.281 510.281 0 0 0 0 3.794.463.463 0 0 1-.007.094c-.015.069-.054.103-.142.109a.464.464 0 0 1-.044.002c-.045-.002-.09-.002-.136-.003-.323-.006-.648-.001-.998-.001v-.527-1.34-.671-.003l.004-.668c0-.147-.039-.231-.17-.308-.893-.528-1.78-1.066-2.67-1.6-.051-.03-.101-.065-.173-.111l.001-.003h-.001zm.362 3.39c.068-.003.119.043.173.138.085.148.174.293.264.44l.015.025c.096.154.194.31.292.47l-1.915 1.176c-.337.207-.673.417-1.014.617-.113.067-.154.143-.154.277.01.977.01 1.955.014 2.932V16H7.7V16h-.002c-.004-.053-.014-.112-.014-.17-.005-1.25-.006-2.501-.015-3.751 0-.142.045-.222.164-.294a467.13 467.13 0 0 0 3.353-2.054l.016-.01a.606.606 0 0 1 .032-.017l.016-.008a.308.308 0 0 1 .033-.013l.012-.004a.157.157 0 0 1 .028-.005l.01-.001zm5.677 3.126l.314.54.346.594v.001c-.158.094-.298.178-.438.259l-3.097 1.798c-.106.062-.189.071-.3.01l-.893-.496-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001l.137-.25.495-.902 1.446.795c.442.243.886.483 1.323.734.121.07.212.072.334 0 .894-.525 1.792-1.043 2.689-1.563.057-.034.118-.061.191-.1z" fill="#29b6f6" stroke-width=".041"/></symbol><symbol viewBox="0 0 500 500" id="kotlin" xmlns="http://www.w3.org/2000/svg"><path d="M500 500H0V0h500L250 250z" fill="#7F52FF"></path></symbol><symbol viewBox="0 0 240 240" id="laravel" xmlns="http://www.w3.org/2000/svg"><path d="M216.05 119.036c-1.433.343-24.945 6.673-24.945 6.673l-19.227-28.622c-.537-.828-.99-1.656.359-1.849 1.345-.196 23.195-4.477 24.182-4.723.99-.245 1.837-.536 3.053 1.267 1.21 1.8 17.836 24.626 18.464 25.506.627.877-.447 1.41-1.883 1.748m-4.101 49.326c.588 1.003 1.176 1.64-.67 2.367-1.843.73-62.243 22.847-63.418 23.39-1.173.546-2.092.73-3.607-1.637-1.51-2.362-21.16-39.264-21.16-39.264l64.03-18.075c1.876-.644 2.317-.405 3.103.822 1.074 1.68 21.143 31.403 21.726 32.4m-103.7-21.087c-.78.202-37.566 9.733-39.525 10.22-1.965.485-1.965.246-2.188-.49-.226-.727-43.728-98.053-44.333-99.271-.605-1.214-.574-2.177 0-2.177.571 0 34.734-3.313 35.944-3.383 1.207-.07 1.08.205 1.526 1.033l49.025 91.818c.84 1.58 1.239 1.81-.452 2.248m94.588-59.77c-3.5-4.58-5.2-3.751-7.357-3.41-2.154.336-27.277 4.915-30.194 5.449-2.918.536-4.758 1.803-2.963 4.53 1.597 2.422 18.113 27.824 21.751 33.42l-65.663 17.066L66.18 49.832c-2.075-3.342-2.507-4.514-7.236-4.28-4.735.23-40.969 3.495-43.55 3.731-2.58.233-5.416 1.479-2.835 8.09 2.583 6.612 43.734 102.82 44.88 105.62 1.149 2.803 4.128 7.345 11.11 5.527 7.157-1.871 31.969-8.894 45.52-12.742 7.163 14.07 21.77 42.619 24.473 46.707 3.607 5.459 6.089 4.56 11.626 2.738 4.325-1.42 67.65-26.129 70.502-27.4 2.855-1.273 4.613-2.184 2.685-5.275-1.419-2.28-18.124-26.558-26.876-39.26 5.993-1.733 27.305-7.888 29.575-8.557 2.646-.779 3.008-2.19 1.572-3.94-1.436-1.755-21.293-28.72-24.79-33.296z" fill="#ff5722" stroke="#ff5722" stroke-width="8.852" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="less" xmlns="http://www.w3.org/2000/svg"><path d="M13.696 2.999V5h2.002v5a2 2 0 0 0 1.999 2 2 2 0 0 0-2 2v5h-2v2h2a2 2 0 0 0 2-2v-4a2 2 0 0 1 2-2h1V11h-1a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2.001zm-.03 12.766v.47a1 1 0 0 0 .03-.236 1 1 0 0 0-.03-.234zM10.566 21v-2.001H8.565v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2.001v-2H8.565a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-.999V13h1a2 2 0 0 1 2 2v3.999A2 2 0 0 0 8.564 21zm.03-12.766v-.47a1 1 0 0 0-.03.236 1 1 0 0 0 .03.234z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="lib" xmlns="http://www.w3.org/2000/svg"><path d="M19 7H9V5h10m-4 10H9v-2h6m4-2H9V9h10m1-7H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 40 40" id="livescript" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -257)" fill="#317eac"><path stroke-width="3.299" d="M5.419 260.18h3.685v34.207H5.419z"/><path stroke-width="3.299" d="M37.074 288.197v3.685H2.867v-3.685z"/><path stroke-width="2.894" d="M29.612 265.658l2.004 2.005L7.428 291.85l-2.004-2.005z"/><path stroke-width="2.325" d="M10.73 262.471h2.835v22.08H10.73z"/><path stroke-width="2.063" d="M15.36 262.519h2.835v17.382H15.36z"/><path stroke-width="1.77" d="M19.99 262.471h2.835v12.802H19.99z"/><path stroke-width="1.422" d="M24.526 262.491h2.835v8.254h-2.835z"/><path stroke-width="1.128" d="M28.783 262.463h2.835v5.197h-2.835z"/><path stroke-width="2.325" d="M34.801 286.545v-2.835h-22.08v2.835z"/><path stroke-width="2.063" d="M34.753 281.914v-2.835H17.371v2.835z"/><path stroke-width="1.77" d="M34.801 277.284v-2.835H21.999v2.835z"/><path stroke-width="1.422" d="M34.781 272.749v-2.835h-8.254v2.835z"/><path stroke-width="1.128" d="M34.809 268.492v-2.835h-5.197v2.835z"/></g></symbol><symbol viewBox="0 0 24 24" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="lua" xmlns="http://www.w3.org/2000/svg"><circle cx="12.203" cy="12.102" r="10.322" fill="none" stroke="#42a5f5"/><path d="M12.33 5.746a6.483 6.381 0 0 0-6.482 6.381 6.483 6.381 0 0 0 6.482 6.38 6.483 6.381 0 0 0 6.484-6.38 6.483 6.381 0 0 0-6.484-6.38zm1.86 1.916a2.329 2.292 0 0 1 2.33 2.293 2.329 2.292 0 0 1-2.33 2.291 2.329 2.292 0 0 1-2.329-2.29 2.329 2.292 0 0 1 2.328-2.294z" fill="#42a5f5" fill-rule="evenodd"/><ellipse cy="4.615" cx="19.631" rx="2.329" ry="2.292" fill="#42a5f5" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="markdown" xmlns="http://www.w3.org/2000/svg"><path d="M2 16V8h2l3 3 3-3h2v8h-2v-5.17l-3 3-3-3V16H2m14-8h3v4h2.5l-4 4.5-4-4.5H16V8z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" preserveAspectRatio="xMidYMid" id="markojs" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -120.96)" stroke-width=".984"><path d="M4.002 126.482c-.655 1.07-1.32 2.14-1.976 3.21-.655 1.06-1.308 2.142-1.963 3.212l.002.002-.002.002c.655 1.07 1.308 2.15 1.963 3.211.655 1.07 1.32 2.141 1.976 3.211h3.33c-.664-1.07-1.318-2.14-1.974-3.21-.653-1.069-1.307-2.145-1.961-3.214.654-1.068 1.308-2.146 1.961-3.215a601.93 601.93 0 0 1 1.974-3.209z" fill="#2196f3"/><path d="M3.999 126.482l-.002.002c.655 1.07 1.31 2.15 1.964 3.212.655 1.07 1.32 2.14 1.974 3.21h3.331c-.664-1.07-1.319-2.14-1.974-3.21-.653-1.068-1.306-2.146-1.96-3.214z" fill="#26a69a"/><path d="M15.203 126.482l.002.002c-.655 1.07-1.31 2.15-1.965 3.212-.655 1.07-1.319 2.14-1.974 3.21h-3.33c.664-1.07 1.318-2.14 1.973-3.21.654-1.069 1.307-2.146 1.961-3.214z" fill="#8bc34a"/><path d="M11.874 126.484c.664 1.07 1.318 2.14 1.974 3.21.653 1.068 1.307 2.146 1.961 3.214-.654 1.069-1.308 2.145-1.961 3.213-.656 1.07-1.31 2.14-1.974 3.21h3.33c.655-1.07 1.319-2.14 1.974-3.21.655-1.06 1.31-2.14 1.966-3.21l-.002-.003.002-.002c-.656-1.07-1.311-2.152-1.966-3.213-.655-1.07-1.319-2.138-1.974-3.209z" fill="#ffc107"/><path d="M16.74 126.482c.665 1.07 1.319 2.14 1.974 3.21.654 1.068 1.306 2.146 1.96 3.214-.654 1.069-1.306 2.145-1.96 3.213-.655 1.07-1.31 2.141-1.974 3.211h3.33c.656-1.07 1.32-2.14 1.974-3.21.655-1.062 1.31-2.141 1.966-3.212l-.002-.002.002-.002c-.655-1.07-1.31-2.152-1.966-3.213-.655-1.07-1.318-2.138-1.973-3.209z" fill="#f44336"/></g></symbol><symbol viewBox="0 0 23 24" id="mathematica" xmlns="http://www.w3.org/2000/svg"><path d="M11.512 1.523l-.073.025-.46.794-.454.763-1.217 2.09H9.29L5.435 3.5l-.1-.047h-.018v.092l.025.163v.086l.132 1.226v.082l.032.252v.082l.22 2.137v.075l.018.082v.06l-2.348.507-.04.015-.457.1-.025.01h-.042l-1.096.244-.04.007-.17.036v.082l.018.01 1.859 2.086.053.052.114.132.804.909v.005l-.053.05-.22.257-2.564 2.875-.01.007v.082l.071.006.295.075 1.697.366v.006l2.139.472h.015v.047l-.036.252v.08l-.046.412v.082l-.036.244v.082l-.045.412v.08l-.05.41v.08l-.036.244v.082l-.046.412v.082l-.05.407v.082l-.032.248V20l-.05.407v.104h.037l3.642-1.6.294-.134h.018l.177.312.539.911.015.032.854 1.465.16.262.404.695.007.022h.092l.005-.022.017-.025.56-.947.014-.042.6-1.033.316-.539.644-1.091.05.013 3.906 1.721h.035v-.085l-.138-1.32v-.082l-.032-.244v-.082l-.035-.245v-.085l-.033-.244v-.081l-.032-.245v-.082l-.032-.244v-.085l-.035-.245v-.082l-.032-.245v-.082l-.033-.244v-.085l-.025-.17v-.053l1.632-.354.043-.008.458-.107h.028v-.01l.23-.05.03-.01h.042l.382-.09.025-.01h.043l.194-.05h.033l1.015-.23.07-.007v-.064l-.015-.013-1.19-1.342-.028-.028-.197-.22-1.428-1.604v-.006l.295-.323.4-.457 2.148-2.408.015-.01v-.065l-.035-.008-1.288-.28-.372-.084-.047-.01-2.481-.544v-.045l.432-4.265v-.02h-.042l-.302.135-.01.014h-.025l-3.307 1.45-.297.135h-.015l-2.028-3.483-.099-.145-.014-.045zm-.001 1.114l1.365 2.323.34.592-.008.025-1.18 1.511-.517.66-.012-.01-.258-.335-.04-.05-1.397-1.787.03-.063 1.378-2.365.287-.491zm4.908 2.039l-.007.025-.168.225-.538.066zm-9.817.004l.053.02.677.3h-.499l-.224-.3zM16.947 5l-.123 1.248-.113-.928.226-.307zm-9.26.156l.053.024.705.309-.757-.175zm7.388.116l.02.168-1.318.403.003-.003.16-.071 1.015-.444zM9.669 6.388l.944 1.204v.01L9.483 7.2zm3.55.172l.21.682-.234.084-.089.022-.702.255.008-.022.776-.982zm-5 .836l.986.356.898.312.048.02 1.054.373.011 3.086-.362-.117-.67-.224-.081-.038-.735-.245-.77-.256-.29-.1-.011-.255-.032-1.195-.01-.287-.015-.894-.013-.297zm6.583 0l-.011.227-.028.9-.008.303-.032 1.475-.01.262-.337.117-.734.245-.77.256-.712.245-.355.117.01-3.086 1.632-.578zm.585.437l.09.735.79-.097-.915 1.302-.018.006.01-.183.018-.877zm-9.451.536l.152.22 1.447 2.049-2.607.968-.05.015-1.972-2.214-.28-.312.003-.01.115-.018.424-.1.14-.021.337-.078.042-.01zm11.146.003l3.284.713.029.01-.022.025-1.954 2.192-.277.312-.092-.036-2.564-.95.475-.681.152-.216zM6.787 8.52h.86l.036 1.258-.013-.006-.763-1.078zm1.358 2.625l.152.06.77.252.712.245.746.247.49.167-.065.092-1.723 2.334-1.015-.302-.082-.017-.035-.015-1.902-.56.938-1.22.981-1.277zm6.73 0l.033.006 1.787 2.327.132.17-.128.036-.032.014-2.196.642-.105.032-.564.17-.018-.003-1.053-1.44-.174-.239-.547-.726-.007-.018.469-.16.769-.254.713-.245.77-.252zm-7.766.305l-.007.02-.405.523-.291-.291.657-.245zm8.802 0l.043.007.578.212.714.27-.661.394-.375-.479-.03-.042-.262-.342zm-10.843.75l-.67.668.355-.397.207-.23zm12.911.016l.068.025.045.042.554.627.042.043.204.228-.255.135zm-6.473.265l.022.015 1.38 1.872.032.05.343.465.008.031-.088.117-.422.629-.047.074-.245.343-.97 1.43-.013.007-1.18-1.72-.096-.16-.493-.708-.008-.037 1.618-2.191.007-.01zm7.827 1.194l.565.633.063.082-.272-.093-.037-.013zm-15.785.148l.297.299-.637.218-.152.05.038-.058zm13.224.47l-.855.448.346.66-.185-.058-.27-.088-1.092-.348.012-.01zm-9.687.255l1.222.356-.006.007-.458.145-.443.135-.032.01-.49.157zm-2.765.048l.318.32 2.007.517-.567.18-.055.004-2.103-.469-.744-.156.007-.006zm14.966.205l.548.188v.003l-.457.1-.043.014-1.069.23zm-10.23.507l.007.227.01.347.025 1.363.025.691-.007.255-.24.107-2.863 1.255.032-.372.033-.255.017-.227.031-.256.037-.407.045-.42.018-.23.032-.251.032-.412.05-.414.013-.14 1.455-.457.003-.014.301-.098zm4.908 0l1.245.39v.014l.312.1 1.146.362.022.23.03.255.043.408.04.42.017.23.033.251.032.412.042.325.078.848-.078-.04-3.025-1.322-.004-.305.06-2.368zm-4.295.617l.015.007.067.107.6.875-.64.531-.034-1.438zm3.671 0h.008l-.005.06-.02.678-.005.214-.479-.223zm-2.888 3.605l.763.915.001.37-.017-.006-.025-.05-.464-.791-.012-.018zm1.53.61l.184.083-.343.586-.018.007.002-.532z" fill="#f44336" fill-rule="evenodd" stroke="#f44336" stroke-width=".7747499999999999" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 720 720" id="matlab" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><path d="M209.247 329.98L52.368 387.638l121.325 85.822 96.752-95.804-61.198-47.674z" fill="#4db6ac" fill-rule="evenodd" stroke-width=".3"/><path d="M480.193 71.446c-13.123 1.784-9.565 1.013-28.4 16.09-18.008 14.418-69.925 100.347-97.673 129.256-24.688 25.722-34.46 12.199-60.102 33.661-25.68 21.494-65.273 64.464-65.273 64.464l63.978 47.32L394.15 222.754c23.948-32.932 23.694-37.266 36.744-71.82 6.384-16.907 17.76-29.9 27.756-45.809 12.488-19.874 30.186-34.855 21.543-33.68z" fill="#00897b" fill-rule="evenodd" stroke-width=".3"/><path d="M478.206 69.796c-31.268-.189-62.068 137.245-115.56 242.691-54.543 107.519-162.235 176.82-162.235 176.82 18.156 8.243 34.681 4.91 54.236 23.394 13.375 16.164 52.09 95.976 75.174 146.117 0 0 18.964-10.297 42.994-27.695 24.03-17.397 53.124-41.896 73.384-70.3 26.883-37.692 47.897-61.043 65.703-75.271 17.806-14.23 32.404-19.336 46.458-20.54 50.238-4.305 124.582 85.792 124.582 85.792S527.267 70.09 478.206 69.796z" fill="#ffb74d" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 24 24" id="merlin" xmlns="http://www.w3.org/2000/svg"><text style="line-height:1.25;-inkscape-font-specification:'Century Gothic Bold'" x="1.953" y="21.178" transform="scale(.99582 1.0042)" font-weight="700" font-size="30.255" font-family="Century Gothic" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-width=".756"><tspan x="1.953" y="21.178" style="-inkscape-font-specification:'Century Gothic Bold'" font-size="22.745">M</tspan></text></symbol><symbol viewBox="0 0 192 191.99999" id="mocha" xmlns="http://www.w3.org/2000/svg"><title>Mocha Logo</title><g transform="translate(-354.75 -262.42) scale(4.835)" fill="#a1887f"><path d="M103.6 69.6c0-.5-.4-1-1-1H83.8c-.5 0-1 .4-1 1 0 3.4.5 15.1 5.5 20.8.2.2.4.3.7.3h8.4c.3 0 .5-.1.7-.3 5-5.6 5.5-17.3 5.5-20.8zm-7.4 18.2h-5.9c-.3 0-.5-.1-.7-.3-3.4-4-3.8-12-3.9-14.8 0-.5.4-1 1-1h13.2c.5 0 1 .4 1 1 0 2.8-.5 10.7-3.9 14.8-.3.2-.5.3-.8.3zM95.1 66.6s3.6-2.1 1.4-5.9c-1.3-2-1.9-3.7-1.4-4.4-1.3 1.6-3.5 3.3-1.1 6.9.8.9 1.2 2.8 1.1 3.4zM91.1 66.9s2.4-1.4.9-4c-.9-1.3-1.3-2.5-.9-2.9-.9 1.1-2.3 2.2-.7 4.7.5.5.7 1.8.7 2.2z"/><path d="M99.3 78.5c-.4 2.7-1.2 5.8-2.9 7.8-.2.2-.4.3-.6.3h-5c-.2 0-.5-.1-.6-.3-1.2-1.5-2-3.5-2.5-5.6 0 0 5.8.8 9.1-.4 2.4-.9 2.5-1.8 2.5-1.8z"/></g></symbol><symbol viewBox="0 0 24 24" id="movie" xmlns="http://www.w3.org/2000/svg"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4h-4z" fill="#ff9800"/></symbol><symbol viewBox="0 0 24 24" id="music" xmlns="http://www.w3.org/2000/svg"><path d="M16 9V7h-4v5.5c-.42-.31-.93-.5-1.5-.5A2.5 2.5 0 0 0 8 14.5a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V9h3m-4-7a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" fill="#ef5350"/></symbol><symbol viewBox="0 0 24 24" id="mxml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#ffa726"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#ab47bc" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#26c6da" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#e53935" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#9ccc65" stroke-width="12.914"/></symbol><symbol viewBox="0 0 24 24" id="nim" xmlns="http://www.w3.org/2000/svg"><path d="M4.464 15.75L2.288 3.78l5.985 7.617L12.08 3.78l3.809 7.617 5.985-7.617-2.177 11.97H4.464m15.234 3.264a1.088 1.088 0 0 1-1.088 1.088H5.553a1.088 1.088 0 0 1-1.089-1.088v-1.089h15.234z" stroke-width="1.088" fill="#ffca28"/></symbol><symbol viewBox="0 0 500 500" id="nix" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.965 36.302)" stroke-width=".395"><path d="M135.59 415.7c0-.295-2.752-5.283-6.116-11.084-3.364-5.801-6.116-10.776-6.116-11.055s9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694 0 .14-11.836.21-26.302.154-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.365.96h-13.229c-10.896 0-13.229-.095-13.229-.538zm167.58-125.61c-.134-.216 1.188-2.863 2.938-5.882 6.924-11.944 84.291-145.75 96.491-166.88 7.143-12.371 13.142-22.465 13.333-22.433.363.062 25.861 43.105 25.861 43.655 0 .174-6.761 11.952-15.026 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.564.564 30.213.564h29.996l.958 1.48c.526.814 3.296 5.547 6.155 10.518 2.859 4.971 5.45 9.29 5.756 9.597.706.705.704.724-.16 1.572-.395.388-3.36 5.323-6.587 10.965-3.228 5.643-6.056 10.387-6.285 10.543-.23.156-19.695.171-43.256.034l-42.84-.249-.804 1.15c-.441.632-7.504 12.736-15.696 26.897l-14.892 25.747H339.03c-8.517 0-20.015.116-25.55.259-6.55.168-10.15.121-10.309-.135zM169.42 132.23c-56.373-.055-102.5-.182-102.5-.282 0-.1 5.617-10.132 12.481-22.294l12.481-22.112h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427-8.139-14.199-14.797-25.917-14.797-26.04 0-.123 2.682-4.853 5.96-10.51s6.003-10.578 6.055-10.934c.086-.586 1.376-.648 13.572-.648 7.413 0 13.463.143 13.446.317-.017.174.222.707.531 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.118.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z" fill="#5075c1"/><path d="M25.289 203.14c-6.098 10.563-6.69 11.711-6.225 12.078.283.224 3.18 5.044 6.44 10.712 3.261 5.668 6.017 10.355 6.124 10.417.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.472.273-1.08 3.094-14.637 26.574L62.06 289.793l12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956.086.05 23.257-39.831 51.492-88.624 11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.097-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.024.037c-.011.003-1.62-.001-1.825 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.568.03-1.147-.03-1.29-.132-.142-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.199 21.131 89.393 155.03 96.276 167 1.502 2.613 2.92 4.803 3.443 5.348.9-1.249 3.531-5.63 7.954-13.219a1342.88 1342.88 0 0 1 10.049-17.76l6.606-11.443c.692-1.403.754-1.818.653-2.117-.162-.48-6.904-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.991-26.32a.73.73 0 0 1-.009-.366l-.426-.913L359.42 72.5c3.69-6.307 6.425-11.042 9.47-16.29 9.159-15.948 12.037-21.189 11.896-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.145.035-.42.112-5.454.195-11.189.185-5.734-.01-11.22.024-12.188.073l-1.76.089-14.997 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.272.05-.255-.148-7.143-11.902-15.306-26.119l-14.36-25.016c-.115-.186-.444-.744-.457-.752-.477-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.042.48.329 1.05 25.338 43.735 25.772 43.985.207.119 14.178.239 31.05.266 26.651.044 30.75.152 31.234.832.308.43 9.988 17.214 21.513 37.296s21.152 36.627 21.394 36.767c.242.14 5.927.243 12.633.23 6.706-.013 12.401.099 12.657.246.132.076.382-.141.852-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.441-9.774-.195-.712-.065-.822 1.156-.98 1.956-.252 57.397-.057 58.07.205.238.092.79-.569 2.594-3.497 1.866-3.067 5.03-8.524 11-18.866 7.22-12.505 13.044-22.784 12.942-22.843-.102-.059-.771-.051-1.489.016l-.046.001c-4.452.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.121.01-2.095.076-2.66.172a.25.25 0 0 0-.062.083z" fill="#7db7e1"/></g></symbol><symbol viewBox="0 0 24 24" id="nodejs" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.85c-.27 0-.55.07-.78.2l-7.44 4.3c-.48.28-.78.8-.78 1.36v8.58c0 .56.3 1.08.78 1.36l1.95 1.12c.95.46 1.27.47 1.71.47 1.4 0 2.21-.85 2.21-2.33V8.44c0-.12-.1-.22-.22-.22H8.5c-.13 0-.23.1-.23.22v8.47c0 .66-.68 1.31-1.77.76L4.45 16.5a.26.26 0 0 1-.11-.21V7.71c0-.09.04-.17.11-.21l7.44-4.29c.06-.04.16-.04.22 0l7.44 4.29c.07.04.11.12.11.21v8.58c0 .08-.04.16-.11.21l-7.44 4.29c-.06.04-.16.04-.23 0L10 19.65c-.08-.03-.16-.04-.21-.01-.53.3-.63.36-1.12.51-.12.04-.31.11.07.32l2.48 1.47c.24.14.5.21.78.21s.54-.07.78-.21l7.44-4.29c.48-.28.78-.8.78-1.36V7.71c0-.56-.3-1.08-.78-1.36l-7.44-4.3c-.23-.13-.5-.2-.78-.2M14 8c-2.12 0-3.39.89-3.39 2.39 0 1.61 1.26 2.08 3.3 2.28 2.43.24 2.62.6 2.62 1.08 0 .83-.67 1.18-2.23 1.18-1.98 0-2.4-.49-2.55-1.47a.226.226 0 0 0-.22-.18h-.96c-.12 0-.21.09-.21.22 0 1.24.68 2.74 3.94 2.74 2.35 0 3.7-.93 3.7-2.55 0-1.61-1.08-2.03-3.37-2.34-2.31-.3-2.54-.46-2.54-1 0-.45.2-1.05 1.91-1.05 1.5 0 2.09.33 2.32 1.36.02.1.11.17.21.17h.97c.05 0 .11-.02.15-.07.04-.04.07-.1.05-.16C17.56 8.82 16.38 8 14 8z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 300 300" id="nodemon" xmlns="http://www.w3.org/2000/svg"><title>nodemon</title><path d="M149.868 20.62c-2.124 0-4.25.55-6.154 1.648L41.899 81.083a12.306 12.306 0 0 0-6.15 10.652v117.633a12.29 12.29 0 0 0 6.152 10.646l101.815 58.766h.001a12.282 12.282 0 0 0 12.291 0l101.84-58.766a12.29 12.29 0 0 0 6.153-10.652V91.738a12.31 12.31 0 0 0-6.146-10.652L156.015 22.27a12.302 12.302 0 0 0-6.153-1.648zM83.303 70.93s11.789 33.031 35.477 31.934l27.74-15.961a7.348 7.348 0 0 1 3.414-.99h.641a7.233 7.233 0 0 1 3.404.99l27.738 15.961c23.69 1.094 35.475-31.934 35.475-31.934 5.233 23.154 1.06 38.641-5.924 48.942l4.541 2.614h.002c2.321 1.327 3.734 3.795 3.737 6.49l-.12 95.811a3.724 3.724 0 0 1-1.855 3.227 3.624 3.624 0 0 1-3.735 0L177.1 206.971c-2.311-1.363-3.742-3.818-3.742-6.48v-44.763a7.44 7.44 0 0 0-3.737-6.465l-15.642-9.01a7.28 7.28 0 0 0-3.715-1.01 7.378 7.378 0 0 0-3.742 1.01l-15.648 9.01c-2.316 1.323-3.729 3.798-3.729 6.467v44.762c0 2.663-1.413 5.1-3.738 6.48l-36.748 21.041a3.571 3.571 0 0 1-3.71 0c-1.173-.65-1.864-1.887-1.864-3.224l-.137-95.812a7.483 7.483 0 0 1 3.74-6.49l4.541-2.615c-6.982-10.302-11.16-25.79-5.925-48.942z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 990 990" id="npm" xmlns="http://www.w3.org/2000/svg"><defs><style>.hncls-1{fill:#cb3837}.cls-2{fill:#fff}</style></defs><title>n</title><path class="hncls-1" d="M113.26 876.74V113.27h763.47v763.47zm143.59-620.4v476.18h240.61V355.63h140.21v376.96h95.457V256.34z" fill="#e53935" stroke-width=".771"/></symbol><symbol id="nunjucks" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.host0{fill:#388e3c}</style><path class="host0" d="M11.2 21.1H8.1l-2.3-7.9v7.9H2.7V2.9h3.1l2.3 7.4V2.9h3.1zM21.3 19.2c0 1-.8 1.9-1.9 1.9h-4.8c-1 0-1.9-.8-1.9-1.9v-3.8l3.2-.7V18h2.3V7.2h3.1v12z"/></symbol><symbol viewBox="0 0 150 150.00001" id="ocaml" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.76136 0 0 .76136 11.616 19.98)"><path d="M83.02 101.645l.023-.062c-.035-.159-.047-.195-.024.062z" fill="none" stroke-width="1.028"/><linearGradient id="hpa" gradientUnits="userSpaceOnUse" x1="-696.735" y1="97.7" x2="-696.735" y2="142.997" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M82.313 138.79c-.471-1.004-1.904-3.621-2.624-4.46-1.562-1.828-1.927-1.966-2.386-4.275-.799-4.02-2.913-11.31-5.405-16.341-1.286-2.596-3.426-4.777-5.385-6.66-1.71-1.652-5.565-4.431-6.237-4.294-6.296 1.257-8.249 7.432-11.21 12.323-1.638 2.705-3.374 5.007-4.665 7.885-1.192 2.646-1.087 5.577-3.128 7.849-2.093 2.333-3.454 4.814-4.48 7.829-.194.574-.747 6.596-1.348 8.015l9.357-.659c8.719.594 6.2 3.936 19.81 3.208l21.487-.665c-.666-1.97-1.584-4.25-1.938-4.991-.599-1.248-1.352-3.69-1.848-4.763z" fill="url(#hpa)" stroke-width="1.028"/><linearGradient id="hpb" gradientUnits="userSpaceOnUse" x1="-666.972" y1="142.12" x2="-666.972" y2="142.12" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><linearGradient id="hpc" gradientUnits="userSpaceOnUse" x1="-675.228" y1="-1.28" x2="-675.228" y2="142.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M109.553 94.296c-1.652 1.193-4.88 4.06-11.902 5.145-3.152.487-6.1.527-9.335.365-1.584-.076-3.077-.157-4.665-.177-.936-.008-4.074-.107-3.919.193l-.349.871c.054.287.169 1.004.2 1.177.129.704.165 1.265.192 1.912.048 1.331-.11 2.719-.043 4.062.141 2.787 1.175 5.326 1.306 8.137.143 3.13 1.69 6.442 3.188 8.998.569.973 1.434 1.084 1.811 2.283.442 1.373.024 2.83.239 4.293.842 5.675 2.477 11.606 5.032 16.728.018.043.038.09.06.128 3.156-.53 6.318-1.665 10.418-2.271 7.517-1.115 17.972-.54 24.688-1.17 16.993-1.597 26.216 6.97 41.478 3.459V22.459c0-11.84-9.594-21.438-21.435-21.438H19.239C7.4 1.021-2.197 10.62-2.197 22.458v46.774c3.067-1.11 7.479-7.635 8.861-9.222 2.419-2.775 2.858-6.315 4.062-8.544 2.743-5.078 3.215-8.57 9.451-8.57 2.907 0 4.061.67 6.027 3.31 1.368 1.834 3.731 5.224 4.837 7.49 1.277 2.615 3.357 6.153 4.272 6.867.677.53 1.35.928 1.976 1.163 1.012.38 1.848-.316 2.525-.855.863-.687 1.235-2.088 2.035-3.957 1.152-2.696 2.408-5.926 3.122-7.054 1.237-1.949 1.658-4.261 2.993-5.381 1.97-1.652 4.54-1.768 5.246-1.908 3.957-.781 5.755 1.906 7.704 3.645 1.276 1.138 3.019 3.432 4.256 6.507.967 2.4 2.199 4.622 2.714 6.008.497 1.339 1.725 3.484 2.453 6.055.661 2.336 2.43 4.125 3.102 5.235 0 0 1.029 2.882 7.285 5.516 1.357.572 4.1 1.501 5.736 2.096 2.718.988 5.351.86 8.704.458 2.391 0 3.686-3.462 4.772-6.234.643-1.639 1.259-6.334 1.678-7.667.406-1.297-.544-2.3.265-3.437.946-1.327 1.508-1.399 2.054-3.129 1.172-3.704 7.95-3.89 11.761-3.89 3.176 0 2.772 3.083 8.16 2.028 3.086-.605 6.059.398 9.335 1.265 2.758.732 5.352 1.566 6.906 3.385 1.005 1.178 3.5 7.08.958 7.331.244.3.423.84.88 1.135-.566 2.226-3.03.64-4.4.355-1.845-.383-3.147.057-4.952.856-3.085 1.374-7.598 1.214-10.286 3.452-2.281 1.898-2.277 6.133-3.34 8.507-.002-.001-2.955 7.6-9.402 12.248z" fill="url(#hpc)" stroke-width="1.028"/><linearGradient id="hpd" gradientUnits="userSpaceOnUse" x1="-735.137" y1="90.833" x2="-735.137" y2="141.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M38.247 105.09c-1.467-.15-2.83-.317-4.256-.605-2.662-.536-5.57-1.06-8.193-1.688-1.592-.385-6.895-2.263-8.048-2.792-2.702-1.246-4.496-4.63-6.609-4.282-1.348.22-2.662.682-3.5 2.042-.685 1.11-.917 3.016-1.391 4.294-.55 1.485-1.5 2.87-2.331 4.284-1.53 2.595-4.282 4.941-5.468 7.469-.239.52-.45 1.101-.649 1.708V144.415a48.57 48.57 0 0 1 4.45.96c11.955 3.19 14.872 3.46 26.598 2.119l1.1-.146c.897-1.867 1.59-8.227 2.171-10.195.454-1.51 1.077-2.712 1.313-4.253.223-1.463-.02-2.858-.146-4.188-.329-3.332 2.427-4.522 3.742-7.384 1.186-2.589 1.871-5.535 2.853-8.181.941-2.54 2.41-6.13 4.918-7.408-.305-.355-5.237-.518-6.554-.65z" fill="url(#hpd)" stroke-width="1.028"/></g></symbol><symbol viewBox="0 0 24 24" id="pdf" xmlns="http://www.w3.org/2000/svg"><path d="M14 9h5.5L14 3.5V9M7 2h8l6 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.853.853 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71l.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M8.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69l.48-.31m3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82-.05.04z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="perl" xmlns="http://www.w3.org/2000/svg"><path d="M12 14c-1 0-3 1-3 2 0 2 3 2 3 2v-1a1 1 0 0 1-1-1 1 1 0 0 1 1-1v-1m0 5s-4-.5-4-2.5c0-3 3-3.75 4-3.75V11.5c-1 0-5 1.5-5 4.5 0 4 5 4 5 4v-1M10.07 7.03l1.19.53c.43-2.44 1.58-4.06 1.58-4.06-.43 1.03-.71 1.88-.89 2.55C13.16 3.55 15.61 2 15.61 2a15.916 15.916 0 0 0-2.64 3.53c1.58-1.68 3.77-2.78 3.77-2.78-2.69 1.72-3.9 4.45-4.2 5.21l.55.08c0 .52 0 1 .25 1.38C14.1 11.31 18 11.47 18 16s-4.03 6-6.17 6C9.69 22 5 21.03 5 16s4.95-5.07 5.83-7.08c.12-.38-.76-1.89-.76-1.89z" fill="#9575cd"/></symbol><symbol viewBox="0 0 24 24" id="php" xmlns="http://www.w3.org/2000/svg"><path d="M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09-.28.22-.71.33-1.29.33h-.87l.53-2.76h.99m-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59l-1.28 6.58m7.25-8.33l-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18.11.12.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35h-1.44M18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76h1m-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75l-1.29 6.58z" fill="#1E88E5"/></symbol><symbol viewBox="0 0 79 78" id="postcss" xmlns="http://www.w3.org/2000/svg"><title>postcss-logo-symbol</title><g transform="translate(5.48 5.52) scale(.85425)" fill="#e53935" fill-rule="evenodd" stroke="#e53935" stroke-width="1.519"><path d="M15.447 32.623c.106.08.29.132.106.29-.132.184-.29.342-.395.553-.105.185-.184.237-.342.106.21-.343.42-.66.63-.95zM68.342 60.24c0 .078.026.13.026.21.053-.105.053-.158.08-.21zm0 .236v-.026zm-5.368 10.277l-4.58-25.402c-.078-.025-.183-.077-.368-.13.053.105.08.184.106.263.13-.026.184-.026.236-.052 0-.026 0-.052.027-.08l4.58 25.404zm-4.737-31.12c-.026.078-.026.158-.026.237 0-.08 0-.16.028-.238zm.026.526c-.026 0-.026 0-.052-.028v.026c.028.026.028.026.054 0zm-.052.21v-.185c-.077.026-.156.026-.262.053.132.05.264.078.264.13z"/><path d="M78.71 33.967c-.052-1.028-.078-2.056-.184-3.083-.184-1.397-.368-2.82-.684-4.19-.237-1.133-.63-2.214-1.026-3.294-.5-1.265-1-2.556-1.632-3.768-1.026-1.95-2.368-3.69-3.605-5.508-.818-1.16-1.87-2.108-2.66-3.294-.447-.685-1.105-1.264-1.763-1.79-1.053-.845-2.158-1.61-3.263-2.347a32.525 32.525 0 0 0-2.58-1.634c-.71-.397-1.473-.713-2.21-1.056-.842-.395-1.658-.87-2.605-1.054-.238-.05-.448-.13-.685-.21-.605-.21-1.184-.447-1.79-.632-.92-.29-1.815-.632-2.763-.87C50.342 1 49.394.843 48.446.71 47.394.555 46.316.5 45.262.397a26.83 26.83 0 0 0-2.026-.184C42.236.16 41.21.16 40.21.134c-.5-.027-1.026-.08-1.526-.053-.763.026-1.526.105-2.29.21-.736.08-1.473.21-2.183.317-.867.105-1.735.158-2.604.264-.816.106-1.658.264-2.473.396-.29.053-.58.158-.87.21-.63.132-1.288.185-1.92.396-1.13.344-2.263.74-3.368 1.16-1.027.422-2.027.87-3 1.397-1 .552-1.948 1.21-2.895 1.844a45.325 45.325 0 0 0-2.66 1.923c-.84.66-1.63 1.397-2.394 2.135-.42.42-.763.922-1.158 1.396-.657.765-1.315 1.502-1.947 2.293-.524.66-1 1.344-1.5 2.03-.893 1.21-1.656 2.502-2.366 3.794-.29.527-.553 1.054-.816 1.58-.395.79-.816 1.555-1.184 2.372-.264.554-.474 1.16-.632 1.766-.367 1.292-.736 2.61-1.078 3.9-.316 1.16-.395 2.372-.42 3.558-.027 1.054.078 2.082.183 3.136.027.264-.13.58.184.79-.105.29-.026.45.13.5-.182.29.08.476-.024.74-.027.052.08.157.13.236 0 .08-.025.185 0 .264.028.237.133.474.133.738 0 .184.157.395.21.58.026.078 0 .21-.053.263-.158.184-.132.342.105.448.133.342.08.5.054.66.052.236-.027.315 0 .368.21.422.29.896.315 1.37 0 .106.053.212.106.343.026 0 0 .5 0 .5.13-.078.237-.104.368-.157.08.342.158.66.263.95.132.21.132.314.08.34.105.474.157.922.34 1.37 0-.5-.05-1-.13-1.475.368.132.684.263.895.263.027-.08.053-.184.08-.237-.158-.157-.29-.394-.448-.552.053.21 0 .29 0 .37-.105-.054-.237-.107-.368-.16.105-.13.21-.263.368-.42 0-.238-.13-.45-.5-.423.158-.052.316-.13.5-.184.29-.157-.026-.447-.026-.816.026-.447-.237-.895-.316-1.37-.132-.737-.105-1.844-.184-2.582-.158-.132-.29.21-.316.237.08.632.158 1.264.21 1.897-.157-.527-.263-1.107-.394-1.74-.027.185-.053.264-.053.37-.13.13-.026.29.053.474-.184-.08-.395-.052-.395-.052v.738c-.262-.264-.34-.474-.473-.66-.052-.21-.08-.42-.13-.63.05-.133 0-.212 0-.29a15.968 15.968 0 0 1-.08-.634c.026-.026-.026-.42-.026-.42.21.025.343.05.474.05-.263-.34-.08-.552.027-.763.053-.106.237-.13.29-.238.21-.395.553-.71.553-1.212 0-.237.08-.5.105-.738.053-.448.105-.896.13-1.344.054-.58 0-1.16.133-1.713.212-.92.475-1.843.764-2.766.21-.66.448-1.29.71-1.95.395-1.028.764-2.056 1.264-3.03.71-1.424 1.526-2.794 2.316-4.19.5-.87 1.026-1.687 1.58-2.53.525-.817 1.05-1.66 1.657-2.425a21.452 21.452 0 0 1 2.79-2.978c1.053-.948 2.053-1.923 3.184-2.793a32.218 32.218 0 0 1 4.685-3.005c1.343-.71 2.737-1.266 4.132-1.793.895-.342 1.868-.5 2.79-.79 1.052-.343 2.105-.5 3.21-.527.71-.027 1.395-.106 2.105-.185.632-.05 1.263-.104 1.948-.183-.08.105-.106.158-.132.21-.288.422-.604.844-.894 1.265-.237.343-.5.712-.737 1.054-.422.555-.87 1.108-1.264 1.688-.605.87-1.158 1.766-1.79 2.635-.63.843-1.315 1.634-1.973 2.45-.868 1.134-1.684 2.293-2.552 3.426-.79 1.08-1.63 2.11-2.394 3.19-.684.947-1.29 1.95-1.948 2.923-.973 1.45-1.947 2.872-2.92 4.322a271.93 271.93 0 0 1-2.316 3.294c-.053.08-.132.104-.21.157-.21.342-.21.527-.29.685-.21.395-.42.79-.658 1.16-.132.21-.316.394-.474.605-.026-.316.42-.474.21-.87-.13.212-.263.396-.394.607l-.316.63c.105.08.29.133.105.29-.08.133-.158.29-.237.423a.954.954 0 0 0 .29-.264c0 .29-.158.526-.29.763-.105.21-.368.37-.552.527.026.027.21.106.237.132.237-.08.316-.21.343-.132.08-.105.158-.184.184-.263.104-.264.262-.474.525-.58.106-.053.184-.132.263-.21.79-.818 1.606-1.608 2.316-2.478 1.106-1.345 2.106-2.74 3.16-4.11.446-.58.973-1.16 1.446-1.714.078.606.026 1.185 0 1.74-.08.974-.132 1.95-.21 2.95-.027.395 0 .79-.027 1.186 0 .105-.08.184-.08.29 0 .263.08.553.08.817-.08.975-.186 1.923-.265 2.898-.027.21.078.422.13.607-.13 1.422.16 2.925-.078 4.427.184-.29.237-.474.237-.658.025-.158 0-.316 0-.5v-.264c.025-.475.13-.975.078-1.45-.053-.527-.053-1.027.053-1.528.053-.21-.026-.474.106-.738v.395c-.026 1.5.027 3.003-.183 4.505-.027.132.08.37-.21.343-.238.474.052.817-.21 1.08-.054.053.05.29.077.448-.106.317-.106.317.052.343.026.58.08 1.106.105 1.66.42-1 .21-2.03.396-3.058.026.422.053.844.026 1.29 0 .687-.026 1.345-.052 2.03 0 .132-.027.264-.053.396-.08.37-.105.738-.237 1.08-.105.264-.052.66-.052.975v1.003c.105.448-.027.685.052.948-.08.265-.105.344-.08.423l.08.395c.527-.053.29.343.5.553-.158.212-.105.29-.105.397 0 .237-.025.448-.052.685 0 .606-.026 1.212-.026 1.792 0 .08.026.157.026.236 0 .054-.026.74-.026.74.053.078 0 .157-.08.236-.025 0-.104-3.347-.104-3.347h-.395c-.052 1.58.08 3.003-.21 4.48-.316.025-.42.078-.764.078-.816 0-1.632 0-2.448.026-.974 0-1.92.026-2.895.026-.472 0-.972.054-1.446.054-.632 0-1.29-.08-1.92-.08-.975 0-1.922.08-2.896.106-.71.026-1.42.026-2.13.053-.475.025-.95.05-1.422.104-.21.026-.395.105-.658.184-.08 0-.263-.026-.42 0-.265.053-.5.21-.765.264-.395.08-.5.184-.448.58v.263c-.026.052.58-.08.58-.08-.054 0-.08.158-.16.29.212-.08.343-.132.475-.184.395.185.737.08 1.052.16 1.026.262 2.078.37 3.13.473.685.053 1.343.08 2.027.105.973.053 1.947.106 2.92.106.816 0 1.606-.08 2.42-.08 1.13 0 2.264.052 3.395.08.237 0 .5-.028.763-.028h1.92c1.712-.052 3.422-.08 5.133-.13.975-.028 1.975-.08 2.948-.107l3-.08c1.158-.026 2.316-.026 3.448-.05.868 0 1.71-.03 2.58-.055.972-.026 1.972-.105 2.946-.157.527-.027 1.054-.08 1.58-.132.632-.052 1.29-.13 1.92-.157.948-.054 1.922-.08 2.87-.133 1.184-.078 2.368-.183 3.578-.21 1.106-.052 2.237-.026 3.343-.052.974-.027 1.948-.08 2.948-.106l1.66-.08s1.104-.026 1.657-.08c.947-.052 1.894-.157 2.842-.183.604-.027 1.21 0 1.815-.027.973-.026 1.973-.08 2.947-.08.367 0 .762.054 1.236.08-.21.185-.342.29-.5.422.105.026.21.08.316.132a.71.71 0 0 1-.42.13c-.054.133-.107.186-.16.45h.474c-.184 0-.342.237-.526.395-.21-.054-.395 0-.5.29.184.104.158.183.132.29-.316.104-.553.21-.42.552-.107.052-.238.105-.37.184-.13.21-.368.263-.316.553.106.025.21.08.29.104-.132.053-.263.132-.395.184-.473.29-.262.422-.157.554-.08.053-.158.105-.237.132.052.237.13.29.157.29a9.3 9.3 0 0 0-.395.316c-.08.237-.185.342-.29.5s-.158.37-.29.527c-.552.607-.947 1.32-1.657 1.793-.264.185-.5.422-.737.66-.474.447-.895.948-1.395 1.37a29.595 29.595 0 0 1-2.052 1.554 151.56 151.56 0 0 1-2.604 1.792c-.474.315-1 .552-1.5.842s-.974.554-1.474.843c-.316.21-.606.5-.948.66-.868.37-1.79.685-2.684 1.028-.87.37-1.5.685-2.158.922-.605.21-1.237.37-1.868.5-.21.054-.448 0-.685.027-.448.08-.895.186-1.343.238-1.158.158-2.316.264-3.473.422-.685.08-1.343.21-2.027.29-.473.026-.973-.026-1.447-.026-.342 0-.71.08-1.053.027-.552-.08-1.105-.21-1.658-.316-.13-.026-.316-.08-.42-.026-.21.106-.396-.052-.607 0-.13.027-.262-.08-.394-.08-.106-.025-.238.028-.37 0-.29-.078-.552-.183-.87-.157-.313.026-.63-.132-.97-.21-.475-.106-.92-.21-1.396-.317a2.38 2.38 0 0 1-.525-.237c-.685 0-1.133-.026-1.554-.185-.368-.13-.71-.315-1.105-.262-.104.026-.183-.026-.29-.026-.08-.106-.157-.317-.235-.317-.526.027-.842-.42-1.29-.553-.236-.08-.42-.343-.657-.422-.58-.237-1.052-.737-1.71-.816-.21-.027-.42-.132-.658-.21.08.104.13.183.21.262-.763-.37-1.473-.79-2.184-1.186-.104-.026-.183-.13-.262-.184l-.71-.474c-.395.08-.553-.08-.66-.132-.71-.5-1.525-.817-2.21-1.37-.29-.238-.63-.396-.84-.686-.37-.448-.817-.764-1.317-1.027-.394-.21-.762-.448-1.13-.685-.185-.132-.37-.29-.37-.58 0-.185-.078-.37-.315-.264-.105-.158-.21-.342-.342-.395-.316-.13-.526-.37-.763-.58s-.42-.5-.71-.605c-.527-.21-.843-.658-1.158-1.027-.738-.87-1.396-1.82-2.08-2.74-.053-.08-.158-.133-.237-.212.105.29.237.527.368.79-.262-.105-.446-.29-.604-.474-.027.027 1.815 3.057 1.815 3.057.16.237.29.475.448.712a.813.813 0 0 1-.79-.422c-.236-.42-.5-.684-1.026-.63a4.588 4.588 0 0 1-.13-.58c-.107 0-.185 0-.37-.027.37.58.685 1.08 1.027 1.66-.133-.08-.21-.132-.265-.158.473.5.815 1.133 1.42 1.45.132.605.816.895.974 1.475-.13-.027-.238-.053-.37-.08-.21-.263-.447-.526-.683-.816.052.184.13.342.236.474.316.395.606.79.974 1.133.132.134.316.187.316.424.21.105.29.13.368.13.054.16-.025.397.29.344.21.395.42.395.71.264.343.343.528.37.764.16 0 .13.026.262.026.368.105-.053.08-.132.08-.264.13.105.21.158.262.21.263.37.5.712.868 1.002.5.422.948.87 1.42 1.265.922.765 1.95 1.398 2.975 1.977 1.264.712 2.475 1.476 3.764 2.16 1.552.818 3.21 1.372 4.92 1.767.632.132 1.237.263 1.87.42.55.16 1.104.397 1.657.528.842.185 1.71.343 2.552.5.183.027.37.054.58.08.235.053.524-.053.577.027.132.21.237.104.395.078.184-.053.395-.053.605-.053.737.026 1.447.184 2.184.132.16 0 .396-.133.528.13.236-.105.368-.105.473-.13.028.236 0 .236-.05.262-.054.026-.133.053-.238.132.947.184 1.842.21 2.63 0 1.37.105 2.554-.053 3.686-.448.105.132.184.316.342.053.052-.08.184-.107.29-.133.236-.053.526-.158.736-.08.238.08.317-.13.5-.13.317 0 .606-.027.896-.08.158-.026.316-.105.5-.158a1.285 1.285 0 0 0-.58-.133c.317-.158.606-.29.896-.42-.053.078-.106.183-.21.183h.367c-.08 0-.185.237-.316.395.946-.237 1.814-.448 2.657-.66-.29-.552.315-.367.526-.684-.263.08-.526.158-.79.21.895-.447 1.816-.842 2.71-1.237-.13.158-.29.237-.525.37.158.025.263.025.342.05.42.133.316-.262.447-.5.5 0 .71-.078.947-.158.263-.08.526-.158.79-.263.42-.184.815-.42 1.236-.63.08-.028.21 0 .316 0 .29-.186.394-.344.473-.318.37.053.63-.08.736-.42.184-.133.316-.238.447-.318.578-.316 1.13-.632 1.71-.948.21 0 .316 0 .368-.027.344-.16.66-.342.975-.527a2.258 2.258 0 0 1-.263-.13c.262-.054.34-.08.5-.133.63-.74 1.5-1.24 2.157-1.82.29-.026.29-.105.29-.157.104-.132.21-.29.34-.396.58-.527 1.21-.975 1.737-1.528a37.16 37.16 0 0 0 2.184-2.374c.63-.738 1.264-1.475 1.79-2.292.737-1.133 1.368-2.293 2.026-3.48.474-.842.895-1.685 1.37-2.528.05-.08.157-.185.236-.185.71-.08 1.422-.13 2.106-.21.158-.026.342-.13.5-.21-.08-.132-.132-.29-.21-.422-.106-.16-.264-.29-.37-.45-.104-.13-.183-.29-.262-.447-.08-.13-.158-.236-.237-.37a9.7 9.7 0 0 1-.45-.894c-.026-.08-.08-.21-.052-.29.474-1.027.658-2.134 1.105-3.162.447-1.054.58-2.24.79-3.373.184-1.08.29-2.16.42-3.24.08-.764.185-1.502.21-2.266.16-1.212.106-2.346.08-3.48-.026-1-.08-2.028-.13-3.03zM12.685 66.405c-.184-.21-.342-.448-.526-.658l.08-.08c.287.317.577.633.866.976-.158-.08-.342-.132-.42-.238zm.42.238c.08-.027.16-.027.238-.053.08.132.132.29.21.448-.368-.027-.552-.185-.447-.395zm27.37 10.883v-.08c.5-.052.973-.105 1.473-.157v.077c-.5.08-.973.13-1.473.158zm6.63-.685c-.367.08-.762.133-1.13.186-.132.026-.29.158-.342-.08-.053.027-.106.027-.158.054.13.394.447.078.71.236-.58.08-1.13.132-1.684.21v-.052c.16-.026.343-.053.5-.08v-.078a7.743 7.743 0 0 0-.79-.053c-.077 0-.183.106-.262.132-.105.026-.21.053-.342.053-.447.026-.894.026-1.316.052-.027 0-.08-.026-.106-.026v-.08c1.763-.236 3.5-.473 5.263-.71.027.052.027.105.053.157-.158 0-.263.055-.395.08zm.396-.262c.606-.08 1.16-.132 1.738-.21-1.21.342-1.605.394-1.737.21zM24.58 23.374c.84-1.16 1.71-2.32 2.552-3.505.263-.345.473-.714.736-1.056.08-.106.185-.158.316-.264l-.026-.05c.105-.133.21-.24.263-.344.134-.21.213-.448.318-.685a.385.385 0 0 1 .105-.103c.37.184.37-.21.5-.343.237-.264.474-.553.684-.817.158-.21.316-.395.448-.632.026-.08-.053-.21-.08-.317h-.078c.08-.052.158-.13.237-.184.026 0 .026 0 .052-.026.158-.238.316-.475.474-.686.315-.42.657-.842 1.025-1.21-.052.13-.105.263-.158.368.027 0 .027.027.053.027.316-.422.658-.817.974-1.24-.027-.025-.053-.052-.08-.052-.13.132-.236.264-.368.396-.026-.027-.052-.053-.08-.053.265-.343.528-.685.79-1.08.053.08.106.184.21.395.107-.263.212-.447.29-.632-.078.08-.183.158-.262.238l-.08-.08.474-.71c.5-.712 1-1.45 1.5-2.162.185-.263.42-.474.58-.738.5-1 1.29-1.792 1.894-2.714.132-.184.316-.342.474-.5.13-.16.237-.106.342.026.71.896 1.42 1.818 2.13 2.714.528.66 1.054 1.29 1.554 1.976.605.844 1.184 1.687 1.79 2.53.684.975 1.368 1.95 2.026 2.95 1 1.477 1.947 2.953 2.947 4.428.737 1.08 1.474 2.135 2.184 3.215h-1.344c-1.236-.025-2.5-.13-3.736-.078-1.684.08-3.394.264-5.078.396-2.132.185-4.29.21-6.42.21-.765 0-1.528.107-2.29.16-.922.052-1.817.105-2.738.13-1.08.054-2.13.08-3.21.107-.606.026-1.237 0-1.895 0zm30.183 12.12v.238c-.026 0-.052.027-.105.027-.105-.37-.21-.766-.342-1.135-.263-.765-.553-1.53-1.027-2.214-.528-.737-1-1.5-1.528-2.265-.13-.185-.316-.343-.474-.5-.553-.607-1.106-1.24-1.816-1.687a21.485 21.485 0 0 0-3.29-1.688 7.374 7.374 0 0 1-.92-.474h.63l4.5-.08c.974-.025 1.922-.025 2.895-.078.236 0 .368.08.5.29.236.395.473.79.736 1.186.027.052.08.13.08.21 0 .58 0 1.186.026 1.766.025.606.08 1.186.104 1.792 0 .606-.053 1.238-.026 1.87.027.897.053 1.82.053 2.74zM26.447 26.67c1.237-.053 2.42-.132 3.632-.185.945-.053 1.92-.08 2.866-.132.395-.025.764-.05 1.158 0-.42.212-.842.423-1.21.686-.474.316-.92.737-1.395 1.08-.475.342-.896.764-1.29 1.212-.5.605-1.053 1.132-1.58 1.712-.37.422-.79.817-1.105 1.265-.447.58-.842 1.21-1.263 1.87.132-2.504.29-4.98.184-7.51zm17.185 25.35c-.843.21-1.71.448-2.58.553-.736.106-1.5.08-2.263.08a25.42 25.42 0 0 1-2.028-.08c-.763-.078-1.526-.157-2.263-.5-.633-.29-1.29-.553-1.92-.87-.634-.316-1.265-.684-1.74-1.264-.34-.423-.815-.765-1.236-1.134.08.316.263.58.553.764-.132.158-.316.08-.58-.343-.078.053-.157.08-.21.106.08-.185.158-.37.237-.527-.105-.21-.237-.448-.342-.66-.21-.342-.42-.71-.605-1.053-.053-.08-.053-.158-.105-.237a5.893 5.893 0 0 1-.37-.475c-.21-.315-.394-.657-.657-.974 0 .08.027.158.027.264-.027 0-.053.026-.053.026l-.554-1.344c-.026 0-.026 0-.052.026l.473 1.74c-.026 0-.052.025-.08.025-.077-.104-.156-.21-.21-.34-.052-.212-.21-.212-.34-.133-.08.053-.133.237-.106.316.185.448.395.896.606 1.344.052.158.105.29.184.448.027.053.106.105.106.184.106.21.185.42.316.606.237.316.5.632.737.948.235.316.445.66.656.975.026.053.105.053.13.08.133.395.58.684.896.526.08.606.737.817 1 1.397a11.957 11.957 0 0 1-.763-.343c-.027.026-.027.052-.054.105.316.158.632.316.92.5.265.16.528.317.765.5.316.29.685.45 1.13.554a.282.282 0 0 0-.05-.107c.736.343 1.5.712 2.078 1-2.737.054-5.658.107-8.685.16 0-.5-.026-.975-.026-1.476 0-.21.052-.395.025-.606-.08-1.21-.08-2.424-.237-3.61-.157-1.264-.157-2.503-.13-3.77.025-.683-.027-1.394-.054-2.08 0-.922 0-1.82.028-2.74 0-.132.053-.237.106-.37h.08c.025.054 0 .133.05.16.08.08.212.21.265.184.157-.106.394-.21.447-.37.13-.315.184-.658.184-.974 0-.236.106-.394.21-.553.054-.08.08-.158.133-.263-.105-.08-.21-.132-.342-.237.106-.29.08-.633.475-.79.052-.027.052-.16.08-.238.025-.213.05-.45.078-.66.052.08.08.105.13.157a.42.42 0 0 1 .054-.08c0-.104-.026-.315 0-.315.316-.053.184-.395.342-.553.025-.028-.027-.107-.027-.16 0-.052 0-.13.026-.13.367-.08.315-.475.552-.66.08-.053.105-.13.21-.263.21.368-.158.553-.184.816.446-.263.578-.895.315-1.08.105-.08.21-.184.29-.29.29-.316.604-.606.868-.922.185-.236.29-.526.474-.763.106-.132.316-.237.474-.317.474-.262.92-.552 1.21-1 .053-.053.132-.105.21-.158.08-.053.238-.053.264-.132.027-.052-.052-.184-.105-.263.104-.053.21-.158.42-.264-.08.158-.105.264-.158.37l.13.13c.238-.184.606-.394.843-.552 0-.025-.132-.13-.132-.13-.157.08-.394.21-.63.316.05-.08.05-.132.08-.158.367-.237.735-.474 1.13-.66.92-.42 1.842-.842 2.763-1.237.158-.08.37-.026.553-.026.078 0 .13 0 .21-.026.42-.132.842-.264 1.263-.37.183-.052.393-.078.58-.078.787.025 1.577.025 2.366.078.342.026.658.105.974.21a9.88 9.88 0 0 1 1.184.5c.447.24.868.502 1.29.792.763.5 1.473 1.054 2.236 1.502.737.448 1.316 1.054 1.79 1.74.58.816 1.237 1.554 1.5 2.555l.394 1.74c.08.316.264.632.185 1-.133.66-.238 1.345-.343 2.004-.052.265-.105.53-.078.79.05.82-.265 1.53-.58 2.268-.106.237-.264.475-.395.738a.798.798 0 0 0 .21.106l.237-.474c.027 0 .027 0 .053.027-.132.368-.237.764-.37 1.133-.314.817-.63 1.66-1.025 2.45-.21.448-.58.817-.842 1.24-.262.368-.473.763-.736 1.106-.237.29-.473.58-.79.79-.71.527-1.447 1.054-2.21 1.476-.473.29-1.026.448-1.552.58zm-14.027-1.4l-.026.027c-.055-.026-.134-.052-.186-.105l-.632-.95c-.052-.078-.08-.157-.052-.262.29.448.58.87.895 1.29zm16.37 3.61c1.183-.5 2.157-1.21 3.05-2.028.133-.132.264-.263.422-.37 1.106-.684 1.92-1.633 2.658-2.687.842-1.212 1.395-2.582 2.08-3.873a2.73 2.73 0 0 1 .157-.29c-.053 3.004.29 5.955.684 8.933-2.973.105-6 .21-9.052.316zm26.683-.79c-.026.053-.08.106-.105.16-.027-.054-.027-.133-.053-.24-.158.423-.5.212-.737.212-1.42.027-2.868.027-4.29.027-1.368 0-2.762 0-4.13.024-.448 0-.922.105-1.37.132-1.078.052-2.157.08-3.236.105-.08 0-.158-.13-.29-.236a1.81 1.81 0 0 1-.158.237c-.028-.052-.08-.104-.133-.183-.026.08-.053.158-.08.21H58c-.053-.368-.158-.71-.158-1.08 0-.79.08-1.58.105-2.372.027-.368 0-.71 0-1.054.106.08.185.133.29.21.052-.103.105-.182.158-.26 0 0-.053-.028-.106-.08.05-.027.104-.08.104-.106.026-.08.08-.158.08-.21 0-.185-.054-.343-.08-.5.026 0 .052 0 .08-.028l.157.79h.08c-.106-.183.236-.342-.053-.552-.026-.027.026-.185.026-.264-.08-.157-.13-.315-.21-.526.026-.026.105-.053.184-.08-.105-.052-.184-.104-.263-.13.263-.238.263-.37.026-.633.054-.025.106-.025.106-.05 0-.238 0-.475-.052-.71-.053-.266.08-.58-.316-.74a.79.79 0 0 0 .105.21s-.08.027-.158.08c-.342-.317-.13-.74-.21-1.213.184.053.316.106.447.16-.053-.186-.184-.397-.263-.634h-.107v-1.74c0 .027.184.027.29.054 0-.027.025-.053.025-.08-.08-.105-.185-.21-.29-.342l.053-.053c-.21-.262-.105-.63-.105-.71V39.4c.264.264-.13.606.264.764v-.263h-.027c-.026-.395-.026-.79-.052-1.186h-.052c-.027.054-.027.08-.054.133h-.052l.158-6.298c.263.342.552.66.736 1 .606 1.108 1.395 2.057 2.132 3.058.632.87 1.21 1.818 1.79 2.714.71 1.08 1.394 2.16 2.105 3.24a81.41 81.41 0 0 0 1.63 2.426c.5.71 1.028 1.396 1.554 2.082.446.606.92 1.212 1.367 1.818.527.738 1.053 1.475 1.58 2.187.262.368.552.737.84 1.106.16.21.396.37.554.5-.025 0-.052 0-.104-.026.08.105.13.184.184.237.29.158.316.316.158.554zM74 46.854v-.185c0 .052.026.13 0 .184zm.895-11.62c-.027 0-.184-.16-.21-.186-.027.08 0 .158-.053.264-.027-.078-.21-.052-.21-.13-.027.368.157.737.13 1.106.08-.053.395-.08.474-.158.027.026.08.052.106.052-.527.396-.395.79-.158 1.24.052.104.21.315.052.526-.052.053.027.21.053.343h.077v.05l-.237.08c-.052-.08-.367-.236-.367-.37v1.346c.263.08.263.448.368.633a.768.768 0 0 0 .107-.21l.027.024c-.027.158-.053.316-.106.475-.052.236-.105.447-.13.684 0 .026.05.08.05.105-.288.66-.13 1.396-.235 2.08-.08.5 0 1.03-.053 1.556-.054.448-.16.922-.264 1.37-.027.08-.08.105-.21.158.052-.316.026-.527-.027-.817-.028 0-.37-.184-.397-.184 0 .37.21.87.29 1.29-.08-.026-.395-.21-.42-.21-.054.316-.054.738-.08 1.08-.027.264-.263.5-.29.79 0 .16.184.264.158.528h.21c0-.526.238-1 .238-1.554h.078c.027.053.106.106.08.132-.053.29-.16.606-.132.896 0 .158.13.316.08.5-.054.16-.08.317-.107.554-.027-.132-.053-.184-.053-.263-.026 0-.263-.027-.29-.027-.026.158.185.316.158.448-.026.026-.052.026-.105.053l-.868-1.266c-.686-1-1.37-2.003-2.054-3.03a6.312 6.312 0 0 1-.475-.79 37.09 37.09 0 0 0-2.71-4.033c-.762-.974-1.37-2.03-2.08-3.055-.656-.975-1.314-1.924-1.972-2.9-.237-.315-.526-.605-.737-.948-.683-1.08-1.29-2.187-1.972-3.267-.58-.897-1.21-1.767-1.816-2.636-.21-.29-.42-.607-.632-.923a.37.37 0 0 1-.052-.182c-.053-.58-.106-1.16-.132-1.713 0-.527.053-1.054.053-1.608v-.474c0-.132.025-.237.025-.37.025-.025.052-.078.078-.104-.763 0-1.553-.028-2.316 0-.5.025-.763-.186-1.105-.555-1-1.133-1.737-2.424-2.605-3.636a162.42 162.42 0 0 0-2.5-3.427c-.685-.922-1.37-1.818-2.053-2.74-.764-1.054-1.5-2.108-2.29-3.162a381.983 381.983 0 0 0-2.895-3.794c-.45-.58-.95-1.133-1.45-1.74.343.054.66.106.975.133l1.264.08c.947.077 1.894.13 2.84.26.79.107 1.58.265 2.396.396 1.738.29 3.448.765 5.106 1.318.974.316 1.92.738 2.87 1.133 2.13.87 4.157 1.924 6.157 3.03.63.343 1 .896 1.472 1.397.685.712 1.37 1.423 2.027 2.16.762.87 1.472 1.766 2.21 2.662.657.79 1.34 1.58 2 2.372.21.237.37.527.552.79.42.633.895 1.24 1.263 1.924.262.502.42 1.082.604 1.635.262.817.526 1.607.79 2.424.183.606.34 1.24.472 1.87.106.423.08.87.21 1.29.16.556 0 1.16.16 1.715.025.053.05.132.078.185.105.104.184.21.026.368-.025.026-.025.13 0 .21.054-.052.08-.105.133-.184 0 .053.025.08.025.105 0 .104-.027.21 0 .315 0 .052.052.13.078.184.053-.054.105-.08.21-.16.237.897.264 1.793.264 2.715 0 .87.157 1.74-.21 2.583.078-.29-.106-.555-.027-.818z"/><path d="M58.08 45.482c.025 0 .052.027.052.027l-.027-.03c0-.025 0-.025-.026 0zm4.157 26.036c-.29.21-.58.395-.948.474-.028-.026-.028-.053-.054-.08.29-.184.605-.368.895-.553.027.05.08.104.106.157zM12.895 35.81c.29-.367.58-.736.894-1.105.025.026.235.08.262.105-.29.37-.685.87-.974 1.265-.054-.053-.133-.237-.185-.264zM5.42 48.725c-.21-.448-.42-.923-.63-1.37a.91.91 0 0 1 .236-.106c.29.42.42.92.632 1.37 0 0-.21.105-.237.105zm6.712-12.65c-.158.238-.316.502-.474.74-.026-.028-.316.104-.342.078.158-.237.552-.66.71-.896.027.026.053.053.106.08zM59.422 72.6c.025 0 .025-.026.052-.026.184.026.394.052.605.052-.344.237-.555.21-.66-.026zm-47.24-35.418c.028-.08.08-.158.133-.237.052 0 .13-.027.13-.027.107-.184.107-.316.212-.474-.026-.026-.053-.026-.08-.053-.157.108-.315.24-.473.345.053.052.053.08.053.132-.21-.027-.29.08-.395.368-.026.08-.158.106-.29.21-.026.054-.052.186-.105.317l.027.028c-.053.053-.132.08-.132.08-.158.157-.342.29-.5.447-.026.08-.052.158-.052.237.185-.184.5-.527.737-.738l.027.027c.105-.158.184-.316.29-.474.025.026.025.052.052.08-.08.21-.158.446-.237.657-.055.026-.134.08-.134.053-.105.08-.184.184-.29.263l-.473.316c-.263.237-.526.447-.816.685-.184.29-.368.553-.58.896.317-.08.396.053.37.317.368.052.395-.237.5-.448.026-.054.053-.16.105-.186.237-.21.5-.394.763-.605.053-.053.053-.16.053-.238 0-.026-.133-.026-.212-.053.237-.264.58-.71.816-1 .132-.08.263-.186.263-.265-.026-.29.158-.368.37-.474-.106-.08-.133-.157-.133-.183z"/><path d="M12.71 36.892c-.105.184-.21.342-.315.527l-.158-.08c-.105.605-.474 1.132-.842 1.237.105.053.21.106.29.08.078-.027.13-.16.183-.238l.71-1.028.238-.396-.105-.105zM3.948 48.46c.132 0 .264.026.42.026 0-.105.133-.08.133-.184h.08c0 .132.026.237.026.37h-.552c-.027-.027-.132-.186-.106-.212zm-.21-1.212c-.08-.08-.21-.158-.21-.237-.027-.104.052-.235.13-.367.054.184.08.342.132.527-.027.025-.053.052-.053.078zm.658-1.687c.105.266.21.556.316.82a.798.798 0 0 0-.21.105c-.105-.264-.237-.554-.342-.817a.652.652 0 0 1 .237-.106zm58.58 25.194c.13-.052.288-.08.5-.13-.238.183-.422.315-.58.473-.027-.026-.053-.053-.08-.053.053-.105.106-.184.16-.29zM30.63 15.074c.157-.106.29-.185.447-.29l.052.052c-.16.21-.29.42-.475.685-.026-.183-.026-.29-.053-.42-.026 0 0 0 .027-.026zm7.71 13.333c.237-.106.474-.21.763-.343-.026.158-.026.264-.026.37a.927.927 0 0 0-.264-.054c-.158.027-.448.238-.58.264-.025 0 .106-.21.106-.237zm19.74 22.346c.052.263.552.395.052.658.08.055.157.08.236.134a.2.2 0 0 1-.052.106c-.053.025-.158.078-.21.05-.027 0-.08-.104-.08-.157 0-.237.027-.474.053-.79z"/></g></symbol><symbol viewBox="0 0 24 24" id="powerpoint" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3H8m5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2h2z" fill="#d14524"/></symbol><symbol viewBox="0 0 67.47 70" id="powershell" xmlns="http://www.w3.org/2000/svg"><path d="M18.545 12.4c-3.014 0-6.08 2.34-6.873 5.248L1.91 53.438c-.793 2.908.996 5.248 4.01 5.248h42.887c3.014 0 6.08-2.34 6.873-5.248l9.761-35.79c.794-2.908-.993-5.248-4.007-5.248h-42.89zm4.848 6.243c.652.04 1.29.33 1.76.86l7.96 9.013-3.957 3.246 3.957-3.244 4.832 5.47c.037.042.06.088.094.131.026.034.057.06.082.096.02.028.032.057.05.086.057.087.105.176.15.267.028.06.055.117.08.178a2.546 2.546 0 0 1 .171.764c.005.073.01.146.008.219-.002.09-.01.178-.021.267a2.53 2.53 0 0 1-.036.217 2.56 2.56 0 0 1-.07.252c-.024.076-.048.15-.08.224a2.547 2.547 0 0 1-.111.22 2.503 2.503 0 0 1-.133.218 2.546 2.546 0 0 1-.147.187c-.058.07-.118.137-.185.202-.027.026-.048.057-.076.082-.037.032-.077.054-.116.084-.038.03-.07.065-.11.093L16.8 52.271a2.552 2.552 0 0 1-3.563-.626 2.553 2.553 0 0 1 .63-3.563l18.349-12.853-3.06-3.467-7.839-8.873a2.549 2.549 0 0 1 .225-3.608 2.546 2.546 0 0 1 1.85-.638zm22.441 28.214c1.377 0 2.255 1.083 1.969 2.43-.287 1.347-1.627 2.433-3.004 2.434l-9.957.006c-1.378 0-2.256-1.083-1.969-2.43.287-1.347 1.626-2.433 3.004-2.434l9.957-.006z" fill="#03a9f4" stroke-width="5.342" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 210 210" id="prettier" xmlns="http://www.w3.org/2000/svg"><title>prettier-icon-dark</title><g transform="matrix(.9 0 0 .9 10.5 10.5)" fill="none" fill-rule="evenodd"><rect fill="#56B3B4" x="165" y="40" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="200" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="135" y="120" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="75" y="120" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="120" width="50" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="160" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="80" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="65" y="20" width="110" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="20" width="40" height="10" rx="5"/><rect fill="#F7BA3E" x="55" y="180" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="55" y="60" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="180" width="30" height="10" rx="5"/><rect fill="#F7BA3E" x="15" y="60" width="30" height="10" rx="5"/><rect fill="#56B3B4" x="95" y="100" width="90" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="100" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="100" width="20" height="10" rx="5"/><rect fill="#BF85BF" x="105" y="40" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="40" width="80" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="140" width="100" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="140" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="135" y="60" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="135" y="80" width="60" height="10" rx="5"/><rect fill="#56B3B4" x="15" width="130" height="10" rx="5"/></g></symbol><symbol viewBox="0 0 80 80" id="protractor" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="hxa"><path transform="scale(1 -1)" fill="#564b55" stroke-width="27.224" d="M-2.983-69.251h69.412v67.108H-2.983z"/></clipPath></defs><g transform="matrix(1.13039 0 0 -1.13039 5.714 82.137)" clip-path="url(#hxa)"><g transform="scale(.1)"><path d="M1180.54 92.324c-5.53 0-9.93-1.797-13.23-5.39-3.29-3.614-5.22-8.594-5.81-14.97h36.02c0 6.583-1.47 11.622-4.4 15.126-2.93 3.496-7.12 5.234-12.58 5.234zm2.84-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.88 6.21-8.83 14.824-8.83 25.84 0 11.101 2.73 19.922 8.21 26.464 5.45 6.524 12.81 9.805 22.02 9.805 8.63 0 15.46-2.851 20.48-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.45c.21-8.086 2.26-14.22 6.12-18.418 3.89-4.18 9.34-6.29 16.38-6.29 7.42 0 14.76 1.563 22 4.669V34.14c-3.68-1.602-7.18-2.746-10.48-3.438-3.28-.684-7.24-1.035-11.89-1.035M1272.34 30.918v44.57c0 5.606-1.28 9.805-3.82 12.559-2.56 2.773-6.56 4.16-12.02 4.16-7.2 0-12.49-1.953-15.84-5.851-3.34-3.895-5.03-10.32-5.03-19.286V30.918h-10.42v68.887h8.47l1.71-9.422h.5c2.14 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.15 2.804 12.88 2.804 8.29 0 14.54-2.011 18.73-6.015 4.19-3.985 6.28-10.391 6.28-19.192V30.918h-10.43M1328.96 38.406c7.1 0 12.27 1.938 15.48 5.813 3.22 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.44 6.25-15.56 6.25-6.11 0-10.79-2.383-14.04-7.129-3.26-4.746-4.88-11.472-4.88-20.136 0-8.797 1.61-15.45 4.84-19.93 3.23-4.484 7.97-6.723 14.22-6.723zm20.85 1.762h-.56c-4.83-7.004-12.02-10.5-21.62-10.5-9.01 0-16.03 3.066-21.04 9.238-5 6.153-7.5 14.922-7.5 26.27 0 11.355 2.51 20.176 7.54 26.465 5.03 6.289 12.03 9.433 21 9.433 9.34 0 16.5-3.398 21.49-10.195h.81l-.43 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.38 9.25M1434.91 38.27c1.85 0 3.63.136 5.34.421 1.72.274 3.09.547 4.1.84v-7.976c-1.15-.559-2.81-.996-5.01-1.36-2.18-.351-4.17-.527-5.94-.527-13.32 0-19.97 7.012-19.97 21.055V91.71h-9.88v5.027l9.88 4.336 4.38 14.707h6.04V99.805h20V91.71h-20V51.16c0-4.15.98-7.333 2.96-9.56 1.97-2.206 4.67-3.331 8.1-3.331M1463.81 65.43c0-8.809 1.76-15.508 5.27-20.118 3.53-4.609 8.69-6.906 15.53-6.906s12.01 2.297 15.56 6.875c3.53 4.602 5.3 11.301 5.3 20.149 0 8.75-1.77 15.41-5.3 19.953-3.55 4.539-8.77 6.824-15.69 6.824-6.82 0-11.99-2.246-15.47-6.73-3.46-4.48-5.2-11.16-5.2-20.047zm52.47 0c0-11.23-2.83-20-8.48-26.309-5.66-6.309-13.47-9.453-23.44-9.453-6.17 0-11.64 1.445-16.42 4.336-4.78 2.89-8.46 7.031-11.06 12.45-2.59 5.401-3.88 11.73-3.88 18.976 0 11.23 2.8 19.968 8.41 26.242 5.61 6.258 13.4 9.402 23.38 9.402 9.64 0 17.3-3.222 22.97-9.62 5.69-6.415 8.52-15.087 8.52-26.024M1591.71 92.324c-5.54 0-9.94-1.797-13.23-5.39-3.3-3.614-5.24-8.594-5.81-14.97h36c0 6.583-1.46 11.622-4.39 15.126-2.93 3.496-7.13 5.234-12.57 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.89 6.21-8.83 14.824-8.83 25.84 0 11.101 2.74 19.922 8.2 26.464 5.46 6.524 12.81 9.805 22.04 9.805 8.62 0 15.45-2.851 20.48-8.523 5.03-5.676 7.54-13.157 7.54-22.461v-6.613h-47.45c.21-8.086 2.25-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.36-6.29 7.43 0 14.77 1.563 22.01 4.669V34.14c-3.69-1.602-7.17-2.746-10.46-3.438-3.3-.684-7.27-1.035-11.91-1.035M1683.5 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12.01 4.16-7.2 0-12.48-1.953-15.83-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M1740.11 38.406c7.12 0 12.28 1.938 15.49 5.813 3.21 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.43 6.25-15.56 6.25-6.12 0-10.8-2.383-14.05-7.129-3.24-4.746-4.88-11.472-4.88-20.136 0-8.797 1.64-15.45 4.85-19.93 3.22-4.484 7.96-6.723 14.21-6.723zm20.87 1.762h-.57c-4.82-7.004-12.03-10.5-21.62-10.5-9.01 0-16.02 3.066-21.03 9.238-5 6.153-7.52 14.922-7.52 26.27 0 11.355 2.52 20.176 7.55 26.465 5.02 6.289 12.02 9.433 21 9.433 9.34 0 16.5-3.398 21.48-10.195h.83l-.44 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.37 9.25M1846.07 38.27c1.85 0 3.64.136 5.36.421 1.7.274 3.07.547 4.08.84v-7.976c-1.13-.559-2.8-.996-5-1.36-2.2-.351-4.18-.527-5.94-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.86v5.027l9.86 4.336 4.4 14.707h6.04V99.805H1855V91.71h-19.98V51.16c0-4.15.98-7.333 2.95-9.56 1.97-2.206 4.68-3.331 8.1-3.331M1894.26 92.324c-5.53 0-9.94-1.797-13.22-5.39-3.31-3.614-5.25-8.594-5.83-14.97h36.01c0 6.583-1.45 11.622-4.38 15.126-2.95 3.496-7.13 5.234-12.58 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.1 9.297-5.9 6.21-8.84 14.824-8.84 25.84 0 11.101 2.73 19.922 8.2 26.464 5.47 6.524 12.81 9.805 22.03 9.805 8.63 0 15.46-2.851 20.49-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.46c.22-8.086 2.26-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.37-6.29 7.42 0 14.75 1.563 22 4.669V34.14c-3.7-1.602-7.17-2.746-10.47-3.438-3.28-.684-7.25-1.035-11.9-1.035M1983.36 49.727c0-6.426-2.4-11.368-7.18-14.844-4.77-3.477-11.47-5.215-20.11-5.215-9.13 0-16.26 1.445-21.37 4.336v9.687a51.32 51.32 0 0 1 10.65-3.964c3.79-.977 7.45-1.457 10.97-1.457 5.46 0 9.64.87 12.57 2.609 2.95 1.738 4.41 4.394 4.41 7.95 0 2.694-1.17 4.98-3.5 6.894-2.32 1.914-6.85 4.152-13.6 6.757-6.41 2.383-10.97 4.473-13.67 6.25-2.71 1.778-4.72 3.81-6.04 6.067-1.31 2.254-1.98 4.96-1.98 8.113 0 5.606 2.29 10.04 6.86 13.281 4.57 3.25 10.84 4.883 18.79 4.883 7.42 0 14.66-1.515 21.74-4.531l-3.71-8.496c-6.9 2.851-13.17 4.277-18.79 4.277-4.94 0-8.67-.77-11.18-2.324-2.52-1.543-3.78-3.691-3.78-6.406 0-1.844.48-3.418 1.42-4.707.95-1.309 2.46-2.54 4.56-3.711 2.09-1.184 6.11-2.871 12.07-5.086 8.16-2.98 13.69-5.98 16.55-8.996 2.87-3.02 4.32-6.809 4.32-11.367M2021.28 38.27c1.85 0 3.64.136 5.35.421 1.71.274 3.09.547 4.09.84v-7.976c-1.14-.559-2.81-.996-5.01-1.36-2.18-.351-4.18-.527-5.93-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.87v5.027l9.87 4.336 4.4 14.707h6.02V99.805h20V91.71h-20V51.16c0-4.15 1-7.333 2.97-9.56 1.98-2.206 4.67-3.331 8.1-3.331M2053.61 30.918h-10.42v68.887h10.42zm-11.31 87.559c0 2.39.59 4.14 1.76 5.253 1.18 1.106 2.65 1.661 4.42 1.661 1.67 0 3.1-.567 4.32-1.7 1.22-1.132 1.82-2.871 1.82-5.214 0-2.344-.6-4.09-1.82-5.247-1.22-1.16-2.65-1.726-4.32-1.726-1.77 0-3.24.566-4.42 1.726-1.17 1.157-1.76 2.903-1.76 5.247M2121.59 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.49l1.69-9.422h.5c2.15 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.16 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2159.29 77.742c0-4.812 1.35-8.465 4.08-10.926 2.72-2.48 6.51-3.71 11.37-3.71 10.19 0 15.28 4.953 15.28 14.831 0 10.344-5.16 15.532-15.47 15.532-4.9 0-8.67-1.32-11.31-3.965-2.63-2.649-3.95-6.555-3.95-11.762zm-5.67-58.387c0-3.73 1.58-6.55 4.72-8.488 3.14-1.922 7.65-2.879 13.52-2.879 8.75 0 15.24 1.309 19.45 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.15 6.32-3.45 7.754-2.31 1.457-6.65 2.168-13.01 2.168h-12.51c-4.74 0-8.43-1.12-11.06-3.386-2.65-2.266-3.97-5.508-3.97-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.23-3.39 3.15-5.754.91-2.371 1.37-5.039 1.37-8.02 0-6.746-2.29-12.128-6.91-16.152-4.61-4.012-10.93-6.023-18.98-6.023-2.05 0-3.98.156-5.78.5-4.45-2.356-6.67-5.305-6.67-8.871 0-1.883.77-3.282 2.34-4.176 1.54-.902 4.21-1.36 7.97-1.36h12.2c7.46 0 13.19-1.574 17.19-4.707 4-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2192.38 2.004 2183.46 0 2171.72 0c-9 0-15.95 1.68-20.82 5.027-4.88 3.352-7.34 8.079-7.34 14.211 0 4.18 1.35 7.813 4.03 10.88 2.68 3.046 6.45 5.116 11.32 6.21-1.77.8-3.24 2.031-4.44 3.711-1.19 1.68-1.78 3.633-1.78 5.84 0 2.52.66 4.707 2.01 6.602 1.34 1.882 3.44 3.71 6.34 5.468-3.56 1.465-6.46 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.26 13.37 6.79 17.452 4.52 4.082 10.93 6.133 19.22 6.133 3.6 0 6.86-.429 9.75-1.27h23.82M2284.61 91.71h-17.54V30.919h-10.43v60.793h-12.31v4.707l12.31 3.766v3.839c0 16.922 7.4 25.391 22.19 25.391 3.65 0 7.93-.73 12.82-2.195l-2.7-8.364c-4.03 1.301-7.46 1.946-10.31 1.946-3.93 0-6.85-1.309-8.73-3.926-1.89-2.617-2.84-6.816-2.84-12.598v-4.472h17.54V91.71M2302.87 65.43c0-8.809 1.76-15.508 5.28-20.118 3.52-4.609 8.7-6.906 15.52-6.906 6.84 0 12.02 2.297 15.57 6.875 3.54 4.602 5.3 11.301 5.3 20.149 0 8.75-1.76 15.41-5.3 19.953-3.55 4.539-8.78 6.824-15.69 6.824-6.83 0-11.99-2.246-15.46-6.73-3.48-4.48-5.22-11.16-5.22-20.047zm52.48 0c0-11.23-2.82-20-8.47-26.309-5.67-6.309-13.48-9.453-23.46-9.453-6.15 0-11.62 1.445-16.4 4.336-4.77 2.89-8.47 7.031-11.06 12.45-2.59 5.401-3.9 11.73-3.9 18.976 0 11.23 2.81 19.968 8.43 26.242 5.6 6.258 13.4 9.402 23.38 9.402 9.63 0 17.28-3.222 22.97-9.62 5.68-6.415 8.51-15.087 8.51-26.024M2403.79 101.074c3.07 0 5.8-.254 8.22-.761l-1.43-9.676c-2.86.633-5.37.933-7.55.933-5.58 0-10.33-2.261-14.3-6.785-3.95-4.531-5.94-10.156-5.94-16.902V30.918h-10.43v68.887h8.62l1.19-12.754h.5c2.56 4.48 5.63 7.949 9.23 10.37 3.61 2.423 7.56 3.653 11.89 3.653M2500.33 69.766l-10.68 28.476c-1.39 3.594-2.81 8.028-4.28 13.262-.93-4.024-2.24-8.438-3.96-13.262l-10.81-28.476zm14.77-38.848l-11.44 29.227h-36.83l-11.32-29.227h-10.81l36.34 92.273h8.98l36.13-92.273h-11.05M2583.07 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2620.76 77.742c0-4.812 1.36-8.465 4.08-10.926 2.73-2.48 6.53-3.71 11.37-3.71 10.2 0 15.28 4.953 15.28 14.831 0 10.344-5.15 15.532-15.45 15.532-4.91 0-8.68-1.32-11.32-3.965-2.64-2.649-3.96-6.555-3.96-11.762zm-5.66-58.387c0-3.73 1.57-6.55 4.71-8.488 3.15-1.922 7.65-2.879 13.53-2.879 8.75 0 15.23 1.309 19.44 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.14 6.32-3.45 7.754-2.31 1.457-6.64 2.168-13 2.168h-12.51c-4.74 0-8.43-1.12-11.07-3.386-2.63-2.266-3.96-5.508-3.96-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.22-3.39 3.14-5.754.92-2.371 1.38-5.039 1.38-8.02 0-6.746-2.3-12.128-6.92-16.152-4.61-4.012-10.92-6.023-18.97-6.023-2.05 0-3.99.156-5.78.5-4.46-2.356-6.67-5.305-6.67-8.871 0-1.883.78-3.282 2.33-4.176 1.55-.902 4.21-1.36 7.98-1.36h12.2c7.46 0 13.18-1.574 17.18-4.707 4.01-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2653.87 2.004 2644.94 0 2633.2 0c-9 0-15.95 1.68-20.83 5.027-4.88 3.352-7.33 8.079-7.33 14.211 0 4.18 1.35 7.813 4.02 10.88 2.69 3.046 6.47 5.116 11.32 6.21-1.77.8-3.23 2.031-4.43 3.711-1.19 1.68-1.79 3.633-1.79 5.84 0 2.52.66 4.707 2.01 6.602 1.35 1.882 3.45 3.71 6.35 5.468-3.56 1.465-6.47 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.25 13.37 6.79 17.452 4.52 4.082 10.92 6.133 19.21 6.133 3.62 0 6.86-.429 9.75-1.27h23.83M2692.7 99.805V55.117c0-5.605 1.27-9.805 3.83-12.566 2.56-2.766 6.57-4.145 12.01-4.145 7.2 0 12.47 1.965 15.81 5.903 3.33 3.945 4.99 10.379 4.99 19.304v36.192h10.44V30.918h-8.62l-1.5 9.25h-.58c-2.13-3.41-5.1-5.988-8.88-7.793-3.8-1.809-8.13-2.707-12.99-2.707-8.37 0-14.65 1.992-18.81 5.977-4.18 3.964-6.26 10.351-6.26 19.101v45.059h10.56M2760.61 30.918h10.43v97.805h-10.43zM2810.67 38.27c6.5 0 11.6 1.789 15.31 5.343 3.71 3.575 5.56 8.555 5.56 14.961v6.23l-10.44-.448c-8.3-.286-14.27-1.583-17.94-3.868-3.66-2.273-5.5-5.82-5.5-10.644 0-3.781 1.14-6.64 3.42-8.613 2.29-1.973 5.48-2.961 9.59-2.961zm23.57-7.352l-2.07 9.805h-.51c-3.44-4.305-6.86-7.227-10.27-8.77-3.42-1.523-7.68-2.285-12.8-2.285-6.83 0-12.17 1.758-16.05 5.273-3.87 3.528-5.81 8.536-5.81 15.032 0 13.906 11.12 21.199 33.37 21.875l11.7.359v4.277c0 5.418-1.17 9.395-3.5 11.985-2.32 2.566-6.03 3.855-11.15 3.855-5.74 0-12.24-1.758-19.49-5.273l-3.21 7.988c3.4 1.836 7.11 3.281 11.16 4.324a47.81 47.81 0 0 0 12.16 1.575c8.23 0 14.3-1.817 18.27-5.461 3.96-3.66 5.93-9.5 5.93-17.54V30.919h-7.73M2893.6 101.074c3.07 0 5.8-.254 8.25-.761l-1.46-9.676c-2.84.633-5.35.933-7.54.933-5.56 0-10.33-2.261-14.3-6.785-3.96-4.531-5.93-10.156-5.93-16.902V30.918h-10.44v68.887h8.61l1.19-12.754h.5c2.57 4.48 5.65 7.949 9.25 10.37 3.6 2.423 7.56 3.653 11.87 3.653M2901.63 6.727c-3.94 0-7.04.558-9.31 1.691v9.121c2.97-.84 6.08-1.25 9.31-1.25 4.14 0 7.3 1.25 9.45 3.77 2.16 2.507 3.24 6.132 3.24 10.859v91.895h10.69V31.797c0-7.95-2.01-14.121-6.04-18.496-4.02-4.383-9.8-6.574-17.34-6.574M2999.96 55.371c0-8.086-2.93-14.394-8.8-18.918-5.87-4.52-13.83-6.785-23.88-6.785-10.9 0-19.27 1.406-25.14 4.219v10.3c3.77-1.59 7.88-2.847 12.31-3.765 4.45-.93 8.85-1.399 13.21-1.399 7.12 0 12.49 1.36 16.09 4.063 3.59 2.695 5.4 6.465 5.4 11.277 0 3.196-.63 5.805-1.91 7.832-1.29 2.024-3.42 3.907-6.42 5.625-2.99 1.711-7.56 3.664-13.67 5.84-8.55 3.059-14.66 6.692-18.32 10.871-3.66 4.2-5.51 9.668-5.51 16.407 0 7.089 2.68 12.714 7.99 16.914 5.32 4.191 12.36 6.289 21.12 6.289 9.13 0 17.54-1.68 25.2-5.032l-3.32-9.304c-7.59 3.183-14.96 4.785-22.13 4.785-5.66 0-10.07-1.223-13.26-3.652-3.19-2.43-4.78-5.809-4.78-10.118 0-3.191.59-5.8 1.76-7.832 1.17-2.031 3.14-3.886 5.95-5.597 2.78-1.688 7.04-3.563 12.79-5.625 9.63-3.426 16.26-7.118 19.89-11.063 3.62-3.937 5.43-9.043 5.43-15.332M741.648 375.406h30c28.965 0 50.227 5.039 63.774 15.117 13.531 10.079 20.32 25.821 20.32 47.247 0 19.832-6.074 34.628-18.191 44.402-12.141 9.758-31.028 14.641-56.692 14.641h-39.211zm172.192 64.246c0-36.062-11.809-63.691-35.434-82.898-23.621-19.219-57.234-28.82-100.847-28.82h-35.911V198.73h-56.445v345.329h99.438c43.14 0 75.457-8.829 96.961-26.465 21.496-17.637 32.238-43.614 32.238-77.942M1099.26 464.691c11.17 0 20.39-.789 27.63-2.371l-5.43-51.718c-7.88 1.894-16.07 2.832-24.57 2.832-22.2 0-40.19-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.514v261.227h43.464l7.32-46.055h2.83c8.66 15.594 19.96 27.95 33.9 37.09 13.93 9.141 28.93 13.699 45 13.699M1206.88 329.82c0-60.308 22.28-90.465 66.85-90.465 44.08 0 66.13 30.157 66.13 90.465 0 59.688-22.21 89.512-66.61 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.95-75.972-32.83-99.898-21.89-23.945-52.35-35.918-91.41-35.918-24.41 0-45.97 5.508-64.7 16.543-18.75 11.016-33.16 26.836-43.23 47.48-10.08 20.625-15.11 44.551-15.11 71.793 0 42.364 10.86 75.43 32.58 99.2 21.73 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.05-24.328 33.06-57.121 33.06-98.379M1558.11 238.887c13.54 0 27.07 2.129 40.62 6.386v-41.816c-6.13-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.59 0-78.88 27.715-78.88 83.144v140.778h-35.68v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.48-9.57 26.34-9.57M1783.44 464.691c11.17 0 20.38-.789 27.62-2.371l-5.43-51.718c-7.88 1.894-16.06 2.832-24.56 2.832-22.2 0-40.2-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.52v261.227h43.46l7.34-46.055h2.82c8.66 15.594 19.95 27.95 33.9 37.09 13.92 9.141 28.93 13.699 45 13.699M1925.05 236.523c20.15 0 36.32 5.625 48.52 16.895 12.21 11.25 18.31 27.051 18.31 47.344v22.676l-33.54-1.407c-26.13-.937-45.16-5.312-57.04-13.105-11.89-7.793-17.82-19.727-17.82-35.781 0-11.661 3.45-20.665 10.39-27.051 6.91-6.387 17.32-9.571 31.18-9.571zm82.66-37.793l-11.11 36.387h-1.87c-12.62-15.918-25.29-26.738-38.04-32.48-12.74-5.742-29.13-8.633-49.13-8.633-25.67 0-45.7 6.934-60.1 20.801-14.41 13.847-21.62 33.457-21.62 58.808 0 26.934 10 47.246 30 60.934 19.99 13.691 50.45 21.172 91.41 22.441l45.09 1.414v13.938c0 16.699-3.88 29.16-11.68 37.441-7.79 8.262-19.88 12.383-36.25 12.383-13.39 0-26.23-1.953-38.5-5.891a294.638 294.638 0 0 1-35.44-13.933l-17.94 39.668c14.17 7.41 29.68 13.035 46.52 16.894 16.85 3.868 32.77 5.789 47.72 5.789 33.22 0 58.31-7.246 75.22-21.726 16.94-14.492 25.4-37.246 25.4-68.262V198.73h-39.68M2220.04 194.004c-39.52 0-69.55 11.543-90.1 34.609-20.55 23.067-30.82 56.172-30.82 99.321 0 43.925 10.74 77.707 32.23 101.339 21.5 23.614 52.56 35.418 93.18 35.418 27.56 0 52.35-5.117 74.41-15.359l-16.78-44.641c-23.46 9.133-42.82 13.704-58.1 13.704-45.19 0-67.79-29.993-67.79-89.981 0-29.293 5.63-51.305 16.89-66.031 11.26-14.707 27.76-22.09 49.48-22.09 24.72 0 48.11 6.152 70.15 18.437v-48.417c-9.92-5.84-20.5-10-31.76-12.52-11.26-2.52-24.93-3.789-40.99-3.789M2451.52 238.887c13.54 0 27.08 2.129 40.63 6.386v-41.816c-6.15-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.6 0-78.9 27.715-78.9 83.144v140.778h-35.66v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.47-9.57 26.33-9.57M2585.92 329.82c0-60.308 22.28-90.465 66.84-90.465 44.09 0 66.15 30.157 66.15 90.465 0 59.688-22.22 89.512-66.62 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.94-75.972-32.83-99.898-21.89-23.945-52.36-35.918-91.4-35.918-24.42 0-45.98 5.508-64.72 16.543-18.74 11.016-33.14 26.836-43.22 47.48-10.07 20.625-15.12 44.551-15.12 71.793 0 42.364 10.87 75.43 32.59 99.2 21.74 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.04-24.328 33.06-57.121 33.06-98.379M2972.33 464.691c11.18 0 20.38-.789 27.63-2.371l-5.43-51.718c-7.87 1.894-16.05 2.832-24.57 2.832-22.2 0-40.19-7.246-53.96-21.731-13.78-14.48-20.67-33.301-20.67-56.453V198.73h-55.51v261.227h43.46l7.33-46.055h2.83c8.66 15.594 19.96 27.95 33.89 37.09 13.94 9.141 28.94 13.699 45 13.699" fill="#100f0d"/><path d="M610.11 372.83c0-170.584-138.257-308.862-308.846-308.862-170.602 0-308.846 138.278-308.846 308.863 0 170.576 138.244 308.846 308.846 308.846 170.59 0 308.846-138.27 308.846-308.846" fill="#e53935" stroke-width="1.029"/><path d="M460.694 521.792l-105.04.958-61.415 61.415-72.096-47.883 12.445-12.438-29.207.26-99.129-166.817H67.357l24.39-24.402-24.57-41.363L294.66 64.049c2.192-.04 4.399-.08 6.603-.08 170.416 0 308.585 138.055 308.846 308.408L460.694 521.792" fill="#d51c2f" stroke-width="1.029"/><path d="M149.093 350.258c0 84.048 68.13 152.151 152.171 152.151 84.028 0 152.139-68.103 152.139-152.151zm342.063-7.017v14.046h44.015c-1.75 59.337-25.556 113.104-63.54 153.419L438.75 477.81l-9.925 9.94 32.875 32.887c-40.314 37.983-94.081 61.79-153.41 63.527l-.015-44.003h-14.035v44.003c-59.34-1.737-113.096-25.556-153.41-63.527l32.887-32.887-9.945-9.92-32.883 32.875c-37.975-40.315-61.781-94.082-63.53-153.419h44.002l-.008-14.034H67.176v-51.511h468.176v51.5h-44.196" fill="#f5f5f5" stroke-width="1.029"/></g></g></symbol><symbol id="pug" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:#c1272d}.hyst1{fill:#efcca3}.st2{fill:#ed1c24}.hyst3{fill:#ccac8d}.hyst4{fill:#fff}.st5{fill:#ff931e}.st6{fill:#ffb81e}.hyst7{fill:#56332b}.hyst8{fill:#442823}.hyst9{fill:#7f4a41}.hyst10{fill:#331712}.st11{fill:#fc6}.st12{fill:#ccc}.st13{fill:#b3b3b3}.st14{fill:#989898}.st15{fill:#323232}.st16{fill:#1e1e1e}.st17{fill:#4c4c4c}.st18{fill:#e6e6e6}.st19{fill:#606060}</style><path class="hyst1" d="M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3z"/><path class="hyst3" d="M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4 2.3 2.5 5.3 5 6.4 8.9 1.1 3.9 1.4 8.9 1.4 10.2 0 1.3.7 1 2.7 0 4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6"/><path class="hyst3" d="M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5z"/><circle cx="92.3" cy="58.1" r="8.8"/><circle class="hyst4" cx="90" cy="54.2" r="2.3"/><path class="hyst1" d="M78.9 57.7s7.9 5.4 12.2 10.7c4.3 5.3 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7l2.6-1.3z"/><path class="hyst3" d="M64.9 54.5c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4-2.3 2.5-5.4 5-6.5 8.9-1.1 3.9-1.5 8.9-1.5 10.2 0 1.3.2 1.4-2.7 0-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5"/><path class="hyst7" d="M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5z"/><path class="hyst8" d="M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6-.5-11z"/><path class="hyst8" d="M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6.5-11z"/><path class="hyst7" d="M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1-1 2.4-4.6 3.1-8.3 3.2-3.6-.1-7.3-.8-8.3-3.2z"/><path d="M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7c0 .3-.1 1.3-.1 2 0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2z"/><path class="hyst9" d="M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6z"/><path class="hyst8" d="M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5z"/><path class="hyst8" d="M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8zM74.8 85s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8z"/><path class="hyst3" d="M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5z"/><path d="M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3-4.4 0-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9z"/><ellipse transform="rotate(-14.465 66.712 61.468)" class="hyst10" cx="66.7" cy="61.5" rx=".8" ry="1.5"/><ellipse transform="rotate(17.235 62.371 61.462)" class="hyst10" cx="62.4" cy="61.5" rx=".8" ry="1.5"/><circle cx="37.2" cy="58.1" r="8.8"/><circle class="hyst4" cx="39.5" cy="54.2" r="2.3"/><path class="hyst9" d="M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1h5.8z"/><path class="hyst1" d="M50 57.7s-7.9 5.4-12.2 10.7c-4.3 5.3-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7L50 57.7z"/><path class="hyst3" d="M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5zM95.8 41.7s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5zM78.7 55.5s-5.9-6.2-13.8-6.4h.1.1c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7zM71.8 42.5s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2zM37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9zM92 73.2s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9z"/><path class="hyst3" d="M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3.9.3 2.1-3.8 10.1-3.4 8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2zM86.9 41.2c.2 0 .3.1.4.1.1 0-.1-.1-.4-.1zM86.9 41.2zM39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3 12.4-5.2zM89.9 28.9s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3-12.4-5.2z"/><path class="hyst7" d="M89.4 28.9s11.6 9.7 15 20.9c3.4 11.2 2 24.8 4.6 26.5 3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6.5-1.1-14.8-17.8-21.5-21.1-8.1-3.8-18.1-4.1-17.4-3.3z"/><path class="hyst8" d="M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1z"/><path class="hyst7" d="M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3-.3 52.2 15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3z"/><path class="hyst8" d="M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1z"/><path class="hyst3" d="M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2zM107.1 74.6s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2z"/><g><circle class="hyst8" cx="54.5" cy="70.5" r=".8"/><circle class="hyst8" cx="49.9" cy="75.3" r=".8"/><circle class="hyst8" cx="48.4" cy="70.5" r=".8"/></g><g><circle class="hyst8" cx="74" cy="70.5" r=".8"/><circle class="hyst8" cx="78.6" cy="75.3" r=".8"/><circle class="hyst8" cx="80.1" cy="70.5" r=".8"/></g></symbol><symbol viewBox="0 0 50 50" id="puppet" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" fill="#fbc02d"><path stroke-width=".283" d="M11.559 249.467h13.587v13.587H11.559zM27.435 265.056h13.587v13.587H27.435zM11.559 281.074h13.587v13.587H11.559z"/><path stroke-width=".256" d="M16.62 251.615l18.305 18.305-3.236 3.236-18.305-18.305z"/><path stroke-width=".256" d="M37.834 271.331L19.53 289.636l-3.237-3.237 18.305-18.304z"/></g></symbol><symbol viewBox="0 0 100 99.999997" id="purescript" xmlns="http://www.w3.org/2000/svg"><path clip-path="url(#SVGID_2_)" d="M98.079 38.548L79.22 19.68l-5.087 5.088L90.447 41.09 74.134 57.41l5.087 5.087 18.858-18.86a3.59 3.59 0 0 0 1.055-2.55 3.578 3.578 0 0 0-1.055-2.54M25.483 42.794l-5.09-5.089L1.53 56.568a3.566 3.566 0 0 0-1.05 2.545c0 .961.373 1.863 1.05 2.542L20.394 80.52l5.089-5.086L9.162 59.113z" fill="#42a5f5" stroke-width="1.192"/><path clip-path="url(#SVGID_2_)" transform="matrix(1.19175 0 0 1.19175 -306.84 -629.047)" fill="#42a5f5" d="M281.841 551.736l6.461 6.037h28.379l-6.461-6.037zM288.302 566.861l-6.463 6.035h28.381l6.463-6.035zM281.838 581.982l6.464 6.035h28.381l-6.463-6.035z"/></symbol><symbol viewBox="0 0 24 24" id="python" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 7.5A2.86 2.86 0 0 1 22 10.36v3.78A2.86 2.86 0 0 1 19.14 17H12c0 .39.32.96.71.96H17v1.68a2.86 2.86 0 0 1-2.86 2.86H9.86A2.86 2.86 0 0 1 7 19.64v-3.75a2.85 2.85 0 0 1 2.86-2.85h5.25a2.85 2.85 0 0 0 2.85-2.86V7.5h1.18m-4.28 11.79c-.4 0-.72.3-.72.89 0 .59.32.71.72.71a.71.71 0 0 0 .71-.71c0-.59-.32-.89-.71-.89m-10-1.79A2.86 2.86 0 0 1 2 14.64v-3.78A2.86 2.86 0 0 1 4.86 8H12c0-.39-.32-.96-.71-.96H7V5.36A2.86 2.86 0 0 1 9.86 2.5h4.28A2.86 2.86 0 0 1 17 5.36v3.75a2.85 2.85 0 0 1-2.86 2.85H8.89a2.85 2.85 0 0 0-2.85 2.86v2.68H4.86M9.14 5.71c.4 0 .72-.3.72-.89 0-.59-.32-.71-.72-.71-.39 0-.71.12-.71.71s.32.89.71.89z"/><path d="M9.264 22.379c-.895-.24-1.581-.799-1.947-1.582-.228-.489-.237-.606-.238-2.957-.001-2.745.057-3.074.666-3.785.193-.226.568-.517.833-.648.47-.23.579-.239 3.839-.288 3.131-.048 3.386-.065 3.814-.264.626-.291 1.07-.687 1.4-1.247.27-.46.278-.522.311-2.29l.034-1.82.932.051c1.075.058 1.504.211 2.098.748.853.77.869.841.869 3.957 0 2.434-.02 2.783-.18 3.075a3.365 3.365 0 0 1-1.337 1.33l-.517.273-3.95.031-3.951.031.068.274c.037.151.164.377.282.503.209.224.262.229 2.433.229h2.22v1.05c0 1.653-.394 2.437-1.54 3.072l-.545.302-2.644.018c-1.455.01-2.782-.018-2.95-.063zm6.12-1.692c.22-.222.253-.325.206-.675-.07-.523-.278-.73-.732-.73-.467 0-.672.217-.735.78-.042.372-.012.496.163.672.3.3.77.28 1.097-.047z" fill="#fc0" stroke="#fc0" stroke-width=".102"/><path d="M9.349 22.38c-.911-.15-1.936-1.074-2.176-1.963-.073-.273-.101-1.279-.079-2.868.033-2.317.047-2.473.27-2.926.13-.263.401-.623.603-.8.674-.592.87-.63 3.484-.675 4.399-.076 4.927-.166 5.705-.967.642-.662.706-.9.774-2.883l.061-1.784.951.055c.523.031 1.11.122 1.304.204.54.225 1.358 1.042 1.472 1.47.153.572.243 3.18.16 4.617-.071 1.23-.093 1.327-.395 1.78-.193.288-.577.647-.966.903l-.647.425-3.922.008c-2.157.004-3.942.028-3.966.052-.115.115.354.82.587.883.14.038 1.181.073 2.314.079l2.06.01v.91c0 1.739-.326 2.446-1.454 3.162l-.631.4-2.543-.011c-1.398-.007-2.733-.043-2.966-.081zm5.98-1.718c.285-.256.313-.328.251-.658-.09-.483-.301-.682-.722-.682-.436 0-.625.193-.715.73-.065.384-.044.453.2.663.358.308.595.295.985-.053z" fill="#fdd835" stroke-width=".102"/><path d="M4.281 17.396c-.88-.215-1.714-.935-2.024-1.747-.149-.389-.168-.804-.142-3.041.027-2.26.054-2.638.215-2.962.259-.519.851-1.092 1.392-1.346.437-.206.632-.217 4.408-.245l3.95-.03-.067-.275a1.367 1.367 0 0 0-.282-.504c-.21-.224-.263-.23-2.433-.23h-2.22l.002-1.143c.003-1.338.157-1.795.84-2.493.746-.763 1.103-.838 4.025-.838 2.961 0 3.28.06 4.067.768.37.333.572.621.728 1.037.201.539.213.735.183 3.072-.035 2.777-.045 2.824-.78 3.598-.787.829-.76.824-4.59.883-3.812.06-3.797.057-4.61.806-.765.706-.917 1.2-.964 3.133l-.04 1.653-.677-.01c-.371-.007-.813-.045-.98-.086zM9.59 5.551c.237-.204.286-.326.286-.72 0-.547-.201-.763-.71-.763-.502 0-.765.248-.765.724 0 .492.141.782.439.902.345.14.444.12.75-.143z" fill="#3c78aa"/></symbol><symbol viewBox="0 0 24 24" id="r" xmlns="http://www.w3.org/2000/svg"><path d="M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1.081 1.081 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z" fill="#1976d2" stroke-width="1.035"/></symbol><symbol viewBox="0 0 24 24" id="raml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="razor" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 11.91c-.11-2.21-1.75-3.54-3.73-3.54h-.08c-2.29 0-3.55 1.8-3.55 3.84 0 2.29 1.53 3.74 3.54 3.74 2.25 0 3.72-1.65 3.83-3.59m-3.81-5.97c1.53 0 2.97.68 4.02 1.74 0-.51.33-.89.83-.89h.11c.74 0 .89.7.89.92v7.9c-.04.52.54.78.87.44 1.27-1.29 2.78-6.69-.79-9.81-3.33-2.92-7.8-2.44-10.18-.8-2.52 1.74-4.14 5.61-2.57 9.22 1.71 3.95 6.61 5.13 9.52 3.95 1.48-.59 2.15 1.4.65 2.05-2.34.99-8.77.89-11.78-4.32-2.03-3.52-1.93-9.71 3.46-12.92C10.81 1.42 16.24 2.1 19.5 5.5c3.45 3.6 3.25 10.3-.1 12.91-1.51 1.18-3.76.03-3.74-1.7l-.02-.56a5.611 5.611 0 0 1-3.99 1.66C8.63 17.81 6 15.15 6 12.13c0-3.05 2.63-5.74 5.65-5.74z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="react" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.11c1.03 0 1.87.84 1.87 1.89 0 1-.84 1.85-1.87 1.85-1.03 0-1.87-.85-1.87-1.85 0-1.05.84-1.89 1.87-1.89M7.37 20c.63.38 2.01-.2 3.6-1.7-.52-.59-1.03-1.23-1.51-1.9a22.7 22.7 0 0 1-2.4-.36c-.51 2.14-.32 3.61.31 3.96m.71-5.74l-.29-.51c-.11.29-.22.58-.29.86.27.06.57.11.88.16l-.3-.51m6.54-.76l.81-1.5-.81-1.5c-.3-.53-.62-1-.91-1.47C13.17 9 12.6 9 12 9c-.6 0-1.17 0-1.71.03-.29.47-.61.94-.91 1.47L8.57 12l.81 1.5c.3.53.62 1 .91 1.47.54.03 1.11.03 1.71.03.6 0 1.17 0 1.71-.03.29-.47.61-.94.91-1.47M12 6.78c-.19.22-.39.45-.59.72h1.18c-.2-.27-.4-.5-.59-.72m0 10.44c.19-.22.39-.45.59-.72h-1.18c.2.27.4.5.59.72M16.62 4c-.62-.38-2 .2-3.59 1.7.52.59 1.03 1.23 1.51 1.9.82.08 1.63.2 2.4.36.51-2.14.32-3.61-.32-3.96m-.7 5.74l.29.51c.11-.29.22-.58.29-.86-.27-.06-.57-.11-.88-.16l.3.51m1.45-7.05c1.47.84 1.63 3.05 1.01 5.63 2.54.75 4.37 1.99 4.37 3.68 0 1.69-1.83 2.93-4.37 3.68.62 2.58.46 4.79-1.01 5.63-1.46.84-3.45-.12-5.37-1.95-1.92 1.83-3.91 2.79-5.38 1.95-1.46-.84-1.62-3.05-1-5.63-2.54-.75-4.37-1.99-4.37-3.68 0-1.69 1.83-2.93 4.37-3.68-.62-2.58-.46-4.79 1-5.63 1.47-.84 3.46.12 5.38 1.95 1.92-1.83 3.91-2.79 5.37-1.95M17.08 12c.34.75.64 1.5.89 2.26 2.1-.63 3.28-1.53 3.28-2.26 0-.73-1.18-1.63-3.28-2.26-.25.76-.55 1.51-.89 2.26M6.92 12c-.34-.75-.64-1.5-.89-2.26-2.1.63-3.28 1.53-3.28 2.26 0 .73 1.18 1.63 3.28 2.26.25-.76.55-1.51.89-2.26m9 2.26l-.3.51c.31-.05.61-.1.88-.16-.07-.28-.18-.57-.29-.86l-.29.51m-2.89 4.04c1.59 1.5 2.97 2.08 3.59 1.7.64-.35.83-1.82.32-3.96-.77.16-1.58.28-2.4.36-.48.67-.99 1.31-1.51 1.9M8.08 9.74l.3-.51c-.31.05-.61.1-.88.16.07.28.18.57.29.86l.29-.51m2.89-4.04C9.38 4.2 8 3.62 7.37 4c-.63.35-.82 1.82-.31 3.96a22.7 22.7 0 0 1 2.4-.36c.48-.67.99-1.31 1.51-1.9z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="readme" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="reason" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm5.119 8.993h2.798c.382 0 .71.025.985.075.275.05.534.159.774.326.244.168.435.386.577.654.145.265.218.598.218 1 0 .552-.112 1.001-.335 1.35-.22.348-.536.638-.947.87l2.16 3.203H12.31l-1.763-2.742h-.77v2.742H8.12v-7.478zm6.594 0h4.676v1.447h-3.018v1.29h2.802v1.447h-2.802v1.848h3.018v1.446h-4.676v-7.478zM9.778 13.37v2.014h.513c.266 0 .49-.014.67-.044.18-.03.329-.1.45-.207a.96.96 0 0 0 .253-.34c.055-.128.082-.297.082-.508 0-.187-.034-.35-.1-.483a.698.698 0 0 0-.343-.317 1.086 1.086 0 0 0-.395-.095 6.012 6.012 0 0 0-.526-.02h-.604z" fill="#f44336" stroke-width="1.067"/></symbol><symbol viewBox="0 0 172 193" id="restql" xmlns="http://www.w3.org/2000/svg"><title>Group</title><g transform="translate(14.767 16.713) scale(.82795)" fill="none"><path d="M171.39 55.799c-.975-6.147-4.673-11.642-10.15-14.805L96.381 3.546C93.217 1.72 89.615.756 85.964.756s-7.253.964-10.415 2.788L10.69 40.992A20.896 20.896 0 0 0 .272 59.035v74.89a20.894 20.894 0 0 0 10.416 18.042l64.859 37.446c3.165 1.827 6.767 2.791 10.417 2.791s7.252-.964 10.415-2.79l64.859-37.445c5.479-3.166 9.178-8.66 10.152-14.808zm-16.516 85.147L90.017 178.39a8.104 8.104 0 0 1-8.108 0l-64.857-37.444a8.109 8.109 0 0 1-4.053-7.021v-74.89a8.109 8.109 0 0 1 4.053-7.021l64.857-37.446c1.254-.725 2.654-1.086 4.054-1.086s2.8.361 4.054 1.086l64.857 37.446a8.106 8.106 0 0 1 4.053 7.021v74.89a8.109 8.109 0 0 1-4.053 7.021z" fill="#83e8c2"/><path d="M158.93 59.035a8.109 8.109 0 0 0-4.053-7.021L90.02 14.568c-1.254-.725-2.654-1.086-4.054-1.086s-2.8.361-4.054 1.086L17.055 52.014a8.106 8.106 0 0 0-4.053 7.021v74.89a8.109 8.109 0 0 0 4.053 7.021l64.857 37.444a8.104 8.104 0 0 0 8.108 0l64.857-37.444a8.109 8.109 0 0 0 4.053-7.021zm-46.766 31.681c.119-.069.242-.118.365-.149.044-.012.088-.01.131-.018.076-.012.152-.029.228-.029l.015.001c.02.001.038.005.059.006.093.005.184.019.273.04l.1.03c.077.025.15.057.223.095.028.014.057.027.084.043.094.057.184.122.263.199.007.008.013.017.021.024.07.071.133.15.188.235.018.029.033.059.05.09.04.072.072.148.099.229a1.512 1.512 0 0 1 .081.46v16.209l-3.278 1.893a1.548 1.548 0 0 0-.678.83 1.533 1.533 0 0 0-.098.514v3.785l-14.038 8.104-.01.004a1.55 1.55 0 0 1-.354.146c-.045.012-.09.011-.135.018-.074.012-.15.029-.225.029l-.014-.001c-.02-.001-.039-.005-.059-.006a1.463 1.463 0 0 1-.273-.041c-.034-.008-.066-.019-.1-.03a1.318 1.318 0 0 1-.223-.094c-.029-.015-.057-.027-.084-.044a1.45 1.45 0 0 1-.263-.198c-.009-.008-.015-.019-.023-.027a1.495 1.495 0 0 1-.185-.232c-.019-.029-.034-.06-.051-.09a1.422 1.422 0 0 1-.098-.229 1.702 1.702 0 0 1-.033-.101 1.487 1.487 0 0 1-.048-.358l-.001-.002v-20.053a1.446 1.446 0 0 1 .727-1.255zM85.24 31.369a1.449 1.449 0 0 1 1.452 0l45.741 26.41a1.45 1.45 0 0 1 0 2.512l-17.366 10.027a1.457 1.457 0 0 1-1.452 0l-15.49-8.943 1.727-.996a1.552 1.552 0 0 0 0-2.688l-13.111-7.57c-.239-.139-.508-.207-.775-.207s-.535.068-.775.207l-3.278 1.893-14.038-8.104a1.451 1.451 0 0 1 0-2.513zM57.59 47.558c.251 0 .501.065.726.194l15.489 8.942-1.727.997a1.552 1.552 0 0 0 0 2.688l1.727.996-15.488 8.943a1.457 1.457 0 0 1-1.452 0L39.499 60.291a1.45 1.45 0 0 1 0-2.512l17.366-10.027c.225-.129.475-.194.725-.194zm-9.56 92.328c-.241 0-.489-.062-.724-.196l-17.365-10.026a1.45 1.45 0 0 1-.726-1.256V75.59c0-.847.694-1.453 1.452-1.453.242 0 .49.062.724.197l17.366 10.025c.449.26.726.738.726 1.257v17.886l-1.727-.997a1.552 1.552 0 0 0-2.327 1.344v15.139c0 .555.295 1.067.775 1.344l3.278 1.894v16.209a1.45 1.45 0 0 1-1.452 1.451zm29.828 14.929a1.452 1.452 0 0 1-2.177 1.257l-17.365-10.026a1.452 1.452 0 0 1-.726-1.257v-17.885l1.726.996c.25.145.515.211.773.211.811 0 1.554-.648 1.554-1.555v-1.993l15.489 8.942c.449.26.726.738.726 1.257zm0-32.768c0 .127-.02.246-.049.36-.009.035-.021.067-.032.101-.026.08-.059.157-.099.229-.017.03-.032.061-.05.09a1.48 1.48 0 0 1-.188.235l-.021.025a1.51 1.51 0 0 1-.264.199c-.026.016-.055.028-.082.043a1.597 1.597 0 0 1-.324.124 1.362 1.362 0 0 1-.278.041c-.018.001-.036.006-.055.006l-.015.001c-.077 0-.155-.018-.233-.03-.043-.007-.084-.005-.125-.017a1.484 1.484 0 0 1-.366-.149l-14.035-8.104v-3.784a1.545 1.545 0 0 0-.776-1.343l-3.276-1.892V91.976c0-.127.02-.246.049-.361.009-.034.021-.066.032-.1a1.33 1.33 0 0 1 .099-.229c.017-.03.032-.062.051-.091.054-.084.116-.163.187-.234l.021-.025c.079-.076.168-.142.263-.199.027-.016.056-.029.084-.043a1.476 1.476 0 0 1 .601-.166c.019 0 .036-.005.055-.005l.015-.001c.078 0 .157.018.236.03.04.007.081.005.122.017.124.031.246.08.366.149l17.361 10.023a1.456 1.456 0 0 1 .726 1.259zm-9.984-45.373a1.448 1.448 0 0 1-.544-.55 1.466 1.466 0 0 1 0-1.413c.121-.219.303-.41.544-.55l14.038-8.104 3.277 1.892c.48.276 1.071.276 1.551 0l3.278-1.893 14.038 8.105a1.45 1.45 0 0 1 0 2.513L86.691 86.7a1.447 1.447 0 0 1-1.452 0zm74.842 51.733c0 .518-.276.997-.726 1.256l-45.741 26.409a1.452 1.452 0 0 1-2.177-1.257v-20.053c0-.519.277-.997.727-1.257l15.488-8.941v1.992c0 .906.743 1.555 1.553 1.555.26 0 .523-.066.774-.21l13.11-7.57a1.55 1.55 0 0 0 .776-1.344v-3.784l14.038-8.105a1.452 1.452 0 0 1 2.177 1.257v20.052zm0-32.764c0 .519-.276.997-.726 1.256l-15.489 8.943v-1.993c0-.906-.744-1.554-1.554-1.554a1.519 1.519 0 0 0-.773.21l-1.727.996V85.616c0-.519.277-.997.727-1.257l17.365-10.025c.234-.135.482-.197.724-.197.758 0 1.453.606 1.453 1.453z" fill="#111d5a"/><g fill="#83e8c2"><path d="M59.402 90.568zM94.485 123.06zM94.771 123.29zM77.775 122.51zM77.072 123.33zM77.418 123.09zM77.856 122.05zM76.749 123.45zM94.119 122.41zM77.131 133.51l-15.489-8.942v1.993c0 .906-.743 1.555-1.554 1.555a1.53 1.53 0 0 1-.773-.211l-1.726-.996v17.885c0 .519.276.997.726 1.257l17.365 10.026a1.452 1.452 0 0 0 2.177-1.257v-20.053a1.454 1.454 0 0 0-.726-1.257zM94.25 122.74zM110.28 111.42zM94.494 100.98c.088-.089.189-.168.303-.232l17.365-10.026-17.365 10.026a1.392 1.392 0 0 0-.303.232zM77.627 122.83zM58.027 90.936zM58.374 90.693zM59.044 90.521l-.015.001c.083-.001.167.015.251.029-.079-.012-.158-.03-.236-.03zM57.819 91.195zM58.696 90.568zM57.589 91.977zM76.043 123.46zM57.67 91.516zM75.677 123.31l-14.035-8.11zM76.401 123.5l.015-.001c-.082.001-.166-.016-.248-.029.078.012.156.03.233.03zM112.16 90.716zM77.662 101.27zM113.64 90.734zM96.237 123.31zM113.33 90.597zM112.89 90.52c-.075 0-.151.018-.228.029.081-.014.162-.029.242-.028l-.014-.001zM141.26 74.137c-.241 0-.489.062-.724.197l-17.365 10.025c-.449.26-.727.738-.727 1.257v17.885l1.727-.996c.25-.145.515-.211.773-.21.81 0 1.554.647 1.554 1.554v1.993l15.489-8.943a1.45 1.45 0 0 0 .726-1.256V75.59c0-.847-.695-1.453-1.453-1.453zM112.96 90.526zM95.523 123.5c.074 0 .15-.018.225-.029-.08.013-.159.028-.238.028l.013.001zM95.451 123.5zM85.238 86.7zM95.078 123.43zM141.26 106.9c-.241 0-.489.062-.724.196l-14.038 8.105v3.784c0 .555-.296 1.067-.776 1.344l-13.11 7.57c-.251.144-.515.21-.774.21-.81 0-1.553-.648-1.553-1.555v-1.992l-15.488 8.941c-.449.26-.727.738-.727 1.257v20.053a1.452 1.452 0 0 0 2.177 1.257l45.741-26.409a1.45 1.45 0 0 0 .726-1.256v-20.053a1.454 1.454 0 0 0-1.454-1.452zM67.871 41.396a1.451 1.451 0 0 0 0 2.513l14.038 8.104 3.278-1.893c.24-.139.508-.207.775-.207s.536.068.775.207l13.111 7.57a1.552 1.552 0 0 1 0 2.688l-1.727.996 15.49 8.943a1.457 1.457 0 0 0 1.452 0l17.366-10.027a1.45 1.45 0 0 0 0-2.512l-45.741-26.41a1.449 1.449 0 0 0-1.452 0zM39.497 57.779a1.45 1.45 0 0 0 0 2.512l17.366 10.027a1.457 1.457 0 0 0 1.452 0l15.488-8.943-1.727-.996a1.552 1.552 0 0 1 0-2.688l1.727-.997-15.489-8.942a1.458 1.458 0 0 0-1.451 0zM49.481 138.43v-16.209l-3.278-1.894a1.55 1.55 0 0 1-.775-1.344v-15.139c0-.906.743-1.555 1.554-1.554.259 0 .523.065.773.21l1.727.997V85.611a1.45 1.45 0 0 0-.726-1.257L31.39 74.33a1.436 1.436 0 0 0-.724-.197c-.758 0-1.452.606-1.452 1.453v52.817c0 .518.276.997.726 1.256l17.365 10.026a1.45 1.45 0 0 0 2.176-1.255zM114.34 108.18l-3.278 1.893 3.278-1.893V91.971zM114.11 91.193zM114.16 91.283z"/></g><g fill="#de5941"><path d="M94.494 100.98a1.45 1.45 0 0 0-.424 1.023v20.053l.001.002c0 .126.02.244.048.358.01.034.021.066.033.101.026.08.059.156.098.229.017.03.032.061.051.09.055.084.115.162.185.232.009.009.015.02.023.027.079.077.169.142.263.198.027.017.055.029.084.044a1.46 1.46 0 0 0 .596.165c.02.001.039.005.059.006.079 0 .158-.016.238-.028.045-.007.09-.006.135-.018.119-.031.238-.08.354-.146l.01-.004 14.038-8.104v-3.785c0-.18.04-.35.098-.514.122-.343.353-.643.678-.83l3.278-1.893V91.977c0-.127-.021-.246-.049-.361-.009-.033-.021-.065-.032-.099a1.266 1.266 0 0 0-.099-.229c-.017-.031-.032-.061-.05-.09a1.425 1.425 0 0 0-.188-.235l-.021-.024a1.41 1.41 0 0 0-.263-.199c-.027-.016-.056-.029-.084-.043a1.509 1.509 0 0 0-.323-.125 1.591 1.591 0 0 0-.273-.04c-.021-.001-.039-.005-.059-.006-.08-.001-.161.015-.242.028-.043.008-.087.006-.131.018-.123.031-.246.08-.365.149l-17.365 10.026a1.447 1.447 0 0 0-.302.233zM77.13 100.74L59.769 90.717a1.424 1.424 0 0 0-.366-.149c-.041-.012-.082-.01-.122-.017-.084-.015-.168-.03-.251-.029-.019 0-.036.005-.055.005-.095.005-.188.02-.278.041-.034.009-.065.02-.099.03a1.406 1.406 0 0 0-.224.095c-.028.014-.057.027-.084.043a1.515 1.515 0 0 0-.263.199l-.021.025c-.07.071-.133.15-.187.234-.019.029-.034.061-.051.091-.04.073-.072.149-.099.229a1.463 1.463 0 0 0-.081.461v16.206l3.276 1.892a1.547 1.547 0 0 1 .776 1.343v3.784l14.035 8.104c.119.068.242.117.366.149.041.012.082.01.125.017.082.014.166.03.248.029.019 0 .037-.005.055-.006.095-.004.188-.019.278-.041.034-.008.065-.019.099-.029.077-.025.152-.058.225-.095.027-.015.056-.027.082-.043.095-.058.185-.123.264-.199l.021-.025c.07-.071.133-.15.188-.235.018-.029.033-.06.05-.09.04-.072.072-.149.099-.229a1.448 1.448 0 0 0 .081-.461v-20.047a1.456 1.456 0 0 0-.726-1.259zM86.689 86.7l17.365-10.026a1.45 1.45 0 0 0 0-2.513l-14.038-8.105-3.278 1.893a1.556 1.556 0 0 1-1.551 0l-3.277-1.892-14.038 8.104c-.241.14-.423.331-.544.55a1.466 1.466 0 0 0 0 1.413c.121.218.303.41.544.55L85.238 86.7a1.447 1.447 0 0 0 1.451 0z"/></g></g></symbol><symbol viewBox="0 0 24 24" id="riot" xmlns="http://www.w3.org/2000/svg"><defs><path d="M13.26 3.04l.58.05.54.07.52.09.49.11.46.13.44.14.41.16.39.17.36.19.33.21.32.22.29.23.26.25.22.22.2.22.19.24.17.24.15.25.15.26.12.27.12.28.1.29.08.31.07.31.05.32.04.34.02.35.01.37v.05l-.02.51-.05.49-.09.48-.13.45-.15.43-.19.4-.22.39-.26.37-.28.34-.31.33-.33.3-.37.28-.39.27-.41.24-.44.22L21 21h-7.04l-3.48-5.14H9.17V21H3V3h9.01l.64.01.61.03zm-4.09 8.52h2.66l.99-.11.75-.35.47-.55.16-.74v-.05l-.17-.75-.47-.54-.74-.32-.96-.11H9.17v3.52z" id="ija"/></defs><use xlink:href="#ija" fill="#ff1744"/><use xlink:href="#ija" fill-opacity="0" stroke="#000" stroke-opacity="0"/></symbol><symbol viewBox="0 0 24 24" id="robot" xmlns="http://www.w3.org/2000/svg"><path d="M12.05 2.804a1.787 1.787 0 0 1 1.788 1.788c0 .661-.357 1.242-.893 1.546v1.135h.893a6.256 6.256 0 0 1 6.256 6.256h.894a.894.894 0 0 1 .893.893v2.681a.894.894 0 0 1-.893.894h-.894v.894a1.787 1.787 0 0 1-1.787 1.787H5.795a1.787 1.787 0 0 1-1.787-1.787v-.894h-.894a.894.894 0 0 1-.894-.894v-2.68a.894.894 0 0 1 .894-.894h.894a6.256 6.256 0 0 1 6.255-6.256h.894V6.138a1.773 1.773 0 0 1-.894-1.546 1.787 1.787 0 0 1 1.788-1.788m-4.022 9.83a2.234 2.234 0 0 0-2.234 2.235 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.234-2.234 2.234 2.234 0 0 0-2.234-2.234m8.043 0a2.234 2.234 0 0 0-2.234 2.234 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.235-2.234 2.234 2.234 0 0 0-2.235-2.234z" fill="#ff5722" stroke-width=".894"/></symbol><symbol viewBox="100 100 800 800" id="rollup" xmlns="http://www.w3.org/2000/svg"><style>.ilst0{fill:url(#ilXMLID_4_)}.ilst1{fill:url(#ilXMLID_5_)}.ilst2{fill:url(#ilXMLID_8_)}.ilst3{fill:url(#ilXMLID_9_)}.ilst4{fill:url(#ilXMLID_11_)}.ilst5{opacity:.3;fill:url(#ilXMLID_16_)}</style><g id="ilXMLID_14_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_4_" x1="444.47" x2="598.47" y1="526.05" y2="562.05" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_15_" class="ilst0" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_4_)"/></g><g id="ilXMLID_2_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_5_" x1="420.38" x2="696.38" y1="475" y2="689" gradientUnits="userSpaceOnUse"><stop stop-color="#BF3338" offset="0"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_10_" class="ilst1" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_5_)"/></g><linearGradient id="ilXMLID_8_" x1="429.39" x2="469.39" y1="517.16" y2="559.16" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_3_" class="ilst2" d="M329.82 813.46c15.58-8.903 122.41-220.34 227.02-320.5s117.96-66.771 60.094-175.83c0 0-221.46 310.49-301.58 464.06" fill="url(#ilXMLID_8_)" stroke-width="1.113"/><g id="ilXMLID_7_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_9_" x1="502.11" x2="490.11" y1="589.46" y2="417.46" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_12_" class="ilst3" d="M373 537c134.4-247.1 152-272 222-272 36.8 0 73.9 16.6 97.9 46.1-32.7-52.7-90.6-88-156.9-89H307.7c-4.8 0-8.7 3.9-8.7 8.7V691c13.6-35.1 36.7-85.3 74-154z" fill="url(#ilXMLID_9_)"/></g><linearGradient id="ilXMLID_11_" x1="450.12" x2="506.94" y1="514.21" y2="552.85" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FBB040" offset="0"/><stop stop-color="#FB8840" offset="1"/></linearGradient><path id="ilXMLID_6_" class="ilst4" d="M556.84 492.96c-104.61 100.16-211.44 311.6-227.02 320.5s-41.732 10.016-55.643-5.564c-14.801-16.582-37.837-43.401 86.802-272.65 149.57-274.99 169.15-302.7 247.05-302.7 40.953 0 82.24 18.473 108.95 51.302 1.447 2.337 2.893 4.785 4.34 7.233-45.738-47.074-145.23-57.98-169.93-.222-25.373 59.204 42.622 125.08 72.335 119.85 37.837-6.677-6.677-93.48-6.677-93.48 57.757 108.95 44.403 75.563-60.205 175.72z" fill="url(#ilXMLID_11_)" stroke-width="1.113"/><linearGradient id="ilXMLID_16_" x1="508.33" x2="450.33" y1="295.76" y2="933.76" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFF" offset="0"/><stop stop-color="#FFF" stop-opacity="0" offset="1"/></linearGradient><path id="ilXMLID_13_" class="ilst5" d="M373.22 547.49c149.57-274.99 169.15-302.7 247.05-302.7 33.719 0 67.661 12.575 93.48 35.277-26.708-30.492-66.326-47.519-105.72-47.519-77.9 0-97.486 27.71-247.05 302.7-124.64 229.25-101.6 256.07-86.802 272.65 2.114 2.337 4.563 4.34 7.122 6.01-13.02-18.919-18.807-62.877 91.922-266.42z" fill="url(#ilXMLID_16_)" opacity=".3" stroke-width="1.113"/></symbol><symbol viewBox="0 0 24 24" id="ruby" xmlns="http://www.w3.org/2000/svg"><path d="M16 9h3l-5 7m-4-7h4l-2 8M5 9h3l2 7m5-12h2l2 3h-3m-5-3h2l1 3h-4M7 4h2L8 7H5m1-5L2 8l10 14L22 8l-4-6H6z" fill="#f44336"/></symbol><symbol viewBox="0 0 144 144" id="rust" xmlns="http://www.w3.org/2000/svg"><path d="M68.252 26.206a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0M25.766 58.451a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m84.97.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m-74.661 4.88a3.252 3.252 0 0 0 1.651-4.29l-1.58-3.574h6.214v28.01H29.823a43.847 43.847 0 0 1-1.42-16.738zm25.994.688v-8.256h14.798c.764 0 5.397.883 5.397 4.347 0 2.877-3.553 3.908-6.475 3.908zm-20.203 44.452a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m52.769.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m1.101-8.076a3.246 3.246 0 0 0-3.856 2.498l-1.787 8.342a43.847 43.847 0 0 1-36.566-.175l-1.787-8.342a3.246 3.246 0 0 0-3.854-2.497l-7.365 1.581a43.847 43.847 0 0 1-3.808-4.488h35.834c.406 0 .676-.074.676-.443V84.527c0-.369-.27-.442-.676-.442h-10.48V76.05h11.335c1.035 0 5.532.296 6.97 6.045.45 1.768 1.44 7.519 2.116 9.36.674 2.065 3.417 6.19 6.34 6.19h18.501a43.847 43.847 0 0 1-4.06 4.7zm19.898-33.468a43.847 43.847 0 0 1 .093 7.612h-4.499c-.45 0-.631.296-.631.737v2.066c0 4.863-2.742 5.92-5.145 6.19-2.288.258-4.825-.958-5.138-2.358-1.35-7.593-3.6-9.214-7.152-12.016 4.409-2.8 8.996-6.93 8.996-12.457 0-5.97-4.092-9.729-6.881-11.572-3.914-2.58-8.246-3.096-9.415-3.096H39.336A43.847 43.847 0 0 1 63.867 28.52l5.484 5.753a3.243 3.243 0 0 0 4.59.105l6.137-5.869a43.847 43.847 0 0 1 30.017 21.38l-4.201 9.487a3.256 3.256 0 0 0 1.652 4.29zm10.477.154l-.143-1.467 4.327-4.036c.88-.82.55-2.472-.574-2.891l-5.532-2.068-.433-1.428 3.45-4.792c.704-.974.058-2.53-1.127-2.724l-5.833-.949-.7-1.31 2.45-5.38c.502-1.095-.43-2.496-1.636-2.45l-5.92.206-.935-1.135 1.36-5.766c.275-1.17-.913-2.36-2.084-2.085l-5.765 1.359-1.136-.935.207-5.92c.046-1.198-1.357-2.135-2.45-1.637l-5.379 2.452-1.31-.703-.95-5.833c-.193-1.183-1.75-1.83-2.723-1.128l-4.796 3.45-1.425-.432-2.068-5.532c-.42-1.127-2.072-1.452-2.89-.576l-4.036 4.33-1.467-.143-3.117-5.036c-.63-1.02-2.318-1.02-2.946 0l-3.117 5.036-1.467.143-4.037-4.33c-.819-.876-2.47-.551-2.89.576l-2.069 5.532-1.426.432-4.795-3.45c-.974-.703-2.53-.055-2.723 1.128l-.951 5.833-1.31.703-5.379-2.452c-1.093-.5-2.496.439-2.45 1.637l.206 5.92-1.136.935-5.765-1.36c-1.171-.272-2.36.915-2.086 2.086l1.358 5.766-.933 1.135-5.92-.206c-1.193-.035-2.134 1.355-1.637 2.45l2.453 5.38-.703 1.31-5.832.949c-1.185.192-1.827 1.75-1.128 2.724l3.45 4.792-.433 1.428-5.532 2.068c-1.123.42-1.452 2.07-.574 2.891l4.328 4.036-.143 1.467-5.035 3.116c-1.02.63-1.02 2.318 0 2.946l5.035 3.117.143 1.467-4.328 4.037c-.878.818-.549 2.468.574 2.89l5.532 2.068.433 1.428-3.45 4.793c-.701.976-.056 2.532 1.129 2.723l5.831.948.703 1.312-2.453 5.378c-.5 1.093.444 2.5 1.638 2.451l5.917-.207.935 1.136-1.358 5.768c-.275 1.168.915 2.355 2.086 2.08l5.765-1.357 1.137.932-.207 5.921c-.046 1.199 1.357 2.136 2.45 1.636l5.379-2.45 1.31.702.95 5.83c.193 1.187 1.75 1.829 2.725 1.13l4.792-3.453 1.427.435 2.069 5.53c.42 1.123 2.072 1.454 2.89.574l4.037-4.328 1.467.146 3.117 5.035c.628 1.016 2.316 1.018 2.946 0l3.117-5.035 1.467-.146 4.036 4.328c.818.88 2.47.549 2.89-.574l2.068-5.53 1.428-.435 4.793 3.453c.974.699 2.53.055 2.722-1.13l.952-5.83 1.31-.703 5.378 2.451c1.093.5 2.493-.435 2.45-1.636l-.206-5.92 1.135-.933 5.765 1.357c1.171.275 2.36-.912 2.085-2.08l-1.358-5.768.932-1.136 5.92.207c1.194.048 2.138-1.358 1.636-2.451l-2.45-5.378.7-1.312 5.833-.948c1.187-.19 1.831-1.747 1.127-2.723l-3.45-4.793.433-1.428 5.532-2.068c1.125-.422 1.454-2.072.574-2.89l-4.327-4.037.143-1.467 5.035-3.117c1.02-.628 1.021-2.315.001-2.946z" fill="#ff7043" stroke-width="1.146"/></symbol><symbol viewBox="0 0 500 500" id="sass" xmlns="http://www.w3.org/2000/svg"><path d="M422.676 96.573c-12.192-47.839-91.508-63.557-166.575-36.892-44.68 15.877-93.029 40.786-127.81 73.311-41.349 38.675-47.943 72.328-45.216 86.395 9.583 49.622 77.585 82.069 105.535 106.126v.144c-8.246 4.05-68.565 34.584-82.684 65.799-14.893 32.932 2.372 56.556 13.804 59.742 35.424 9.859 71.764-7.866 91.311-37.01 18.853-28.12 17.28-64.422 9.086-82.487 11.3-2.976 24.476-4.314 41.218-2.36 47.248 5.52 56.517 35.017 54.747 47.366-1.77 12.35-11.681 19.14-14.998 21.186-3.317 2.045-4.326 2.766-4.05 4.287.405 2.215 1.94 2.137 4.758 1.652 3.894-.656 24.804-10.042 25.709-32.828 1.14-28.933-26.587-61.302-75.684-60.45-20.216.354-32.933 2.268-42.123 5.69-.681-.774-1.363-1.547-2.084-2.307-30.35-32.382-86.46-55.285-84.088-98.824.866-15.823 6.372-57.5 107.817-108.052 83.104-41.415 149.637-30.009 161.135-4.76 16.427 36.08-35.554 103.137-121.858 112.812-32.88 3.684-50.198-9.059-54.498-13.804-4.536-4.995-5.204-5.218-6.909-4.287-2.753 1.533-1.01 5.938 0 8.574 2.583 6.712 13.15 18.603 31.176 24.515 15.863 5.205 54.459 8.063 101.156-9.99 52.283-20.255 93.12-76.523 81.125-123.548zM200.213 340.34c3.92 14.5 3.487 28.016-.564 40.248a65.289 65.289 0 0 1-3.225 7.97c-3.12 6.477-7.316 12.534-12.442 18.132-15.653 17.069-37.507 23.532-46.88 18.092-10.122-5.874-5.048-29.944 13.083-49.11 19.52-20.636 47.602-33.903 47.602-33.903l-.039-.079 2.465-1.35z" fill="#ec407a" stroke="#ec407a" stroke-width="16.286552999999998"/></symbol><symbol viewBox="0 0 300 300" id="sbt" xmlns="http://www.w3.org/2000/svg"><path d="M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z" fill="#0277bd" stroke-width="1.638"/></symbol><symbol viewBox="0 0 256 256" id="scala" xmlns="http://www.w3.org/2000/svg"><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M59.607 50.647l149.097-21.982v49.488L59.607 100.135zM59.593 114.08L208.69 92.098v49.488L59.593 163.568zM59.587 177.358l149.097-21.982v49.488L59.587 226.846z"/><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M62.425 91.414l95.605 30.923-2.832 8.757-95.605-30.922zM113.084 61.13l95.604 30.922-2.832 8.757-95.605-30.922zM62.425 154.79l95.605 30.922-2.833 8.758-95.604-30.923zM113.097 124.408l95.604 30.923-2.832 8.757-95.605-30.922z"/></symbol><symbol viewBox="0 0 24 24" id="settings" xmlns="http://www.w3.org/2000/svg"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="shaderlab" xmlns="http://www.w3.org/2000/svg"><path d="M9.11 17H6.5l-4.91-5L6.5 7h2.61l1.31-2.26L17.21 3l1.87 6.74L17.77 12l1.31 2.26L17.21 21l-6.79-1.74L9.11 17m.14-.25l5.13 1.38L11.42 13H5.5l3.75 3.75m6.87.38L17.5 12l-1.38-5.13L13.15 12l2.97 5.13M9.25 7.25L5.5 11h5.92l2.96-5.13-5.13 1.38z" fill="#1976d2"/></symbol><symbol viewBox="0 0 24 24" id="slim" xmlns="http://www.w3.org/2000/svg"><path d="M6.959 2.5a4.605 4.605 0 0 0-4.615 4.615v9.957a4.605 4.605 0 0 0 4.615 4.615h9.957a4.605 4.605 0 0 0 4.615-4.615V7.115A4.605 4.605 0 0 0 16.916 2.5zm4.938 2.691a6.811 6.811 0 0 1 6.81 6.813H13.43L9.938 7.287l.699 4.717H5.086a6.811 6.811 0 0 1 6.81-6.813z" fill="#f57f17"/></symbol><symbol id="smarty" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.iust0{fill:#ffce00}</style><path class="iust0" d="M9.14 20.606c0 .556.398.953.954.953h3.812c.556 0 .953-.397.953-.953v-.953H9.141zM12 2.5c-3.653 0-6.671 3.018-6.671 6.671 0 2.303 1.112 4.289 2.859 5.48v2.144c0 .556.397.953.953.953h5.718c.556 0 .953-.397.953-.953V14.65c1.747-1.191 2.86-3.177 2.86-5.48 0-3.653-3.019-6.671-6.672-6.671zm2.7 10.563l-.794.555v2.224h-3.812v-2.224l-.794-.555A4.712 4.712 0 0 1 7.235 9.17 4.78 4.78 0 0 1 12 4.405a4.78 4.78 0 0 1 4.765 4.765 4.712 4.712 0 0 1-2.065 3.892z"/></symbol><symbol viewBox="0 0 200 200" id="snyk" xmlns="http://www.w3.org/2000/svg"><title>Group 2</title><g transform="translate(15.255 18.22) scale(1.8477)" fill="none" fill-rule="evenodd"><path d="M65.161 24.997c-1.656 5.974-5.255 23.587-5.255 23.587s-6.618-2.464-14.148-2.476h-.055c-.413.002-.822.012-1.23.026v41.649h6.677v.003h5.815v-.003h20.858c.111-8.177-2.036-27.066-2.036-27.066-1.088-2.279.46-7.668.46-7.668-8.869-9.092-11.086-28.051-11.086-28.051zm-3.357 43.958c5.476 0 1.381 4.64.9 5.168H52.35c.944-1.18 4.504-5.168 9.453-5.168z" fill="#607d8b" stroke-width="1.6"/><path d="M26.366 24.995s-2.217 18.961-11.087 28.053c0 0 1.548 5.391.46 7.669 0 0-2.15 18.895-2.038 27.066h19.273v.003h7.079v-.003h5.744V46.107h-.025c-7.532.013-14.151 2.478-14.151 2.478s-3.6-17.615-5.255-23.59zm3.264 43.96c4.95 0 8.51 3.987 9.452 5.168H28.73c-.479-.528-4.573-5.168.9-5.168z" fill="#90a4ae" stroke-width="1.6"/><g transform="translate(23.76 77.45) scale(1.5998)"><g transform="translate(17.526)"><path d="M7.357.06H.177v.075C.177 2.64 2.345 4.67 4.89 4.67 7.431 4.67 9.6 2.64 9.6.135V.059z" fill="#455a64"/><path d="M1.972.06v.075a2.692 2.692 0 1 0 5.386 0V.059z" fill="#fff"/><path d="M5.496.06H4.234c-.012 0-.023.005-.034.007.157.033.243.388.21.624a.721.721 0 0 1-.71.617c.102.471.487.85.997.922a1.188 1.188 0 0 0 1.35-1.007C6.112.743 5.881.06 5.495.06z" fill="#37474f"/></g><path d="M7.552.06H.372v.075c0 2.505 2.17 4.535 4.712 4.535 2.544 0 4.712-2.03 4.712-4.535V.059z" fill="#455a64"/><path d="M2.168.06v.075a2.692 2.692 0 1 0 5.385 0V.059z" fill="#fff"/><path d="M5.692.06H4.428c-.01 0-.022.005-.032.007.156.033.242.388.21.624a.72.72 0 0 1-.712.617c.104.471.488.85.999.922A1.187 1.187 0 0 0 6.24 1.223C6.308.743 6.078.06 5.69.06z" fill="#37474f"/></g><path d="M25.514-.27l-4.202 7.697C19.838 10.17 6.858 34.465 6.858 43.243v.516L12.8 59.573c-.8 7.258-2.203 21.643-1.78 28.21h5.73c-.354-3.787.648-17.008 1.903-28.25l.076-.677-1.075-2.892c3.694-3.868 6.285-9.193 8.073-14.261l.174 1.235 5.869 9.629 2.291-.983c.058-.024 5.935-2.523 11.643-2.523 5.672 0 11.646 2.5 11.702 2.525l2.29.976 5.86-9.626.23-1.608c1.769 5.117 4.358 10.536 8.07 14.49l-1.127 3.035.076.678c1.259 11.286 2.266 24.564 1.916 28.252h5.677c.406-6.567-1.05-20.952-1.848-28.208l5.838-15.817v-.514c0-8.779-12.876-33.074-14.347-35.816L65.923-.27l-5.897 41.229-2.723 4.478c-2.628-.882-7.1-2.11-11.603-2.11-4.498 0-8.94 1.225-11.557 2.108l-2.722-4.476-2.07-14.452a.832.832 0 0 0 .006-.071l-.016-.004zm-3.166 18.39l1.206 8.407c-.46 3.143-2.561 15.47-8.198 23.24l-2.598-6.99c.325-4.554 5.067-15.462 9.59-24.656zm46.763 0c4.523 9.194 9.267 20.104 9.592 24.657L76.166 49.6c-6.09-8.553-8-22.459-8.166-23.73z" fill="#607d8b" stroke-width="1.6"/></g></symbol><symbol viewBox="0 0 24 24" id="solidity" xmlns="http://www.w3.org/2000/svg"><path d="M5.8 14.05l6.253 8.61 6.252-8.61-6.254 3.807z" fill="#0288d1" stroke-width="4.553" stroke-linejoin="round"/><path d="M12.051 1.347L5.8 11.833l6.252 3.807 6.254-3.807z" fill="#0288d1" stroke-width="5.025" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 120 120" id="sonar" xmlns="http://www.w3.org/2000/svg"><style>.a,.b{fill:#fff}.b{stroke:#fff;stroke-miterlimit:10}</style><path d="M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422.426-.427.709-.853.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957c0-.995.141-1.991.425-2.986-4.265-2.702-8.53-3.838-14.787-3.838z" fill="#1e88e5" stroke-width="1.422"/></symbol><symbol viewBox="0 0 412 395" id="stylelint" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-white</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#cfd8dc" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 412 395" id="stylelint_light" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-black</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#546e7a" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 200.00001 200.00001" id="stylus" xmlns="http://www.w3.org/2000/svg"><path d="M126.814 155.9c14.64-17.51 16.362-35.595 5.024-69.18-7.177-21.24-19.09-37.602-10.334-50.807 9.329-14.065 29.135-.43 12.63 18.371l3.301 2.297c19.806 2.296 29.566-24.83 14.783-32.58C113.179 3.621 79.02 42.803 94.09 88.156c6.458 19.232 15.5 39.613 8.18 55.83-6.314 13.923-18.514 22.103-26.695 22.39-17.079.862-5.74-38.32 13.922-48.08 1.722-.861 4.162-2.01 1.866-4.88-24.256-2.727-38.464 8.468-46.645 24.112-23.825 45.497 45.21 62.29 82.095 18.371z" fill="#c0ca33" stroke-width="1.435"/></symbol><symbol viewBox="0 0 24 24" id="swc" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jba"><stop offset="0" stop-color="#791223"/><stop offset="1" stop-color="#d92f3c"/></linearGradient><linearGradient xlink:href="#jba" id="jbb" x1="12.356" y1="21.559" x2="12.356" y2="2.949" gradientUnits="userSpaceOnUse"/></defs><path d="M6 3c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6 3 6.5V19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.5c0-.5-.17-.93-.46-1.27l-1.39-1.68C18.88 3.21 18.47 3 18 3H6zm-.07 1h12l.94 1H5.12l.81-1z" fill="url(#jbb)"/><path style="line-height:125%" d="M11.053 11.918h-.008c-.244.022-.475.054-.676.11a2.9 2.9 0 0 0-.856.412 3.399 3.399 0 0 0-.67.683 9.36 9.36 0 0 0-.586.95c-.07.131-.134.244-.201.365v.001h-.002l-.768 1.372-.003-.001c-.136.253-.264.485-.38.686-.123.212-.26.39-.411.539a1.599 1.599 0 0 1-.52.34c-.04.016-.092.024-.138.036h-.567v1.383H5.834v-.001c.245-.02.477-.053.679-.11a2.9 2.9 0 0 0 .856-.411c.245-.185.469-.413.67-.683.195-.275.39-.591.585-.95.07-.131.135-.244.202-.366l.004.001.002-.002.02-.038H10.948v-1.378h-.19v-.001H9.624c.125-.234.246-.452.355-.64.123-.21.259-.39.41-.538.152-.148.325-.26.52-.34.04-.015.091-.024.136-.035h.57V13.3h-.002v-1.381h-.56v-.001z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol viewBox="0 0 24 24" id="swift" xmlns="http://www.w3.org/2000/svg"><path d="M17.09 19.72c-2.36 1.36-5.59 1.5-8.86.1A13.807 13.807 0 0 1 2 14.5c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17z" fill="#fe5e2f"/></symbol><symbol viewBox="0 0 24 24" id="table" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5m4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1v-2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 200 200" id="terraform" xmlns="http://www.w3.org/2000/svg"><g transform="translate(177.03 -58.705) scale(.92881)" fill="#5c6bc0" stroke="#b0aff5" stroke-linejoin="round"><g stroke-width=".288"><path transform="skewY(26.439) scale(.89541 1)" d="M-203.8 170.95h64.714v51.88H-203.8zM-124.37 171.04h64.714v51.88h-64.714zM-124.37 236.09h64.714v51.88h-64.714z"/></g><path transform="skewY(-22.59) scale(-.92328 1)" stroke-width=".284" d="M-19.172 128.27h62.76v51.88h-62.76z"/></g></symbol><symbol viewBox="0 0 24 24" id="test-js" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="test-jsx" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="test-ts" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tex" xmlns="http://www.w3.org/2000/svg"><g font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-linejoin="miter"><text style="line-height:125%" x="9.914" y="364.919"><tspan x="9.914" y="364.919" font-size="287.5">T</tspan></text><text style="line-height:125%" x="136.374" y="435.558"><tspan x="136.374" y="435.558" font-size="287.5">E</tspan></text><text style="line-height:125%" x="307.819" y="361.201"><tspan x="307.819" y="361.201" font-size="287.5">X</tspan></text></g></symbol><symbol viewBox="0 0 24 24" id="todo" xmlns="http://www.w3.org/2000/svg"><path d="M3 5h6v6H3V5m2 2v2h2V7H5m6 0h10v2H11V7m0 8h10v2H11v-2m-6 5l-3.5-3.5 1.41-1.41L5 17.17l4.59-4.58L11 14l-6 6z" fill="#42a5f5"/></symbol><symbol id="travis" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style id="jkstyle2">.jkst0{fill:#cb3349}.jkst1{fill:#f4edae}.jkst2{fill:#e6ccad}.jkst3{fill:#656c67}.jkst4{fill:#e5caa3}.jkst5{fill:#c7b39a}.jkst6{fill:#ebd599}.jkst7{fill:#2d3136}.jkst8{fill:#edf6fa}.jkst9{opacity:.8}.jkst10{opacity:.75;fill:#ebd599}</style><g id="jkg99" transform="translate(11.017 12.484) scale(.8858)"><g id="jkg10"><path class="jkst0" d="M47.781 86.572s-31.118 21.903-32.335 30.247l2.335-.48S55.045 91.64 84.584 88.628l.669-3.749z" id="jkpath4" fill="#cb3349"/><path class="jkst0" d="M96.629 83.442l-24.511 17.385 1.325 1.063c.999-.806 43.539-13.798 43.539-13.798l8.969-5.623c-6.018.749-29.322.973-29.322.973z" id="jkpath6" fill="#cb3349"/><path class="jkst0" d="M117.932 104.469c17.405 0 43.495-17.046 43.495-17.046l-8.434-1.605c-.417.417-13.6-.462-13.6-.462l-6.258-1.738-14.951 17.036-1.217 2.956c1.075-.437.965.859.965.859z" id="jkpath8" fill="#cb3349"/></g><path class="jkst0" d="M174.728 158.832l-5.377 1.514-24.843-.537-15.541-12.085-18.784 4.7-21.726-1.88-12.166 13.294-22.828 6.819-11.398-3.534-.574-.494 5.116 12.527s11.588 12.424 18.061 13.885c6.472 1.461 18.165-.105 26.935-1.463 8.769-1.357 15.764-4.489 18.582-9.603 2.818-5.117 3.236-6.578 3.236-6.578s8.353 11.797 15.556 13.155c7.203 1.357 28.605-5.952 28.605-5.952s13.051-3.549 15.346-8.038c2.297-4.489 8.353-19.209 8.353-19.209zM44.456 169.038l-.361-.166-2.013-1.736z" id="jkpath12" fill="#cb3349"/><g id="jkg97"><path class="jkst1" d="M195.832 70.085a48.125 48.125 0 0 0-.21-2.009 26.472 26.472 0 0 0-.215-1.424c-1.793-1.509-3.831-2.851-5.952-4.071-2.299-1.343-4.704-2.546-7.159-3.663-2.438-1.15-4.942-2.191-7.461-3.207a134.313 134.313 0 0 0-3.798-1.477c-1.269-.495-2.55-.956-3.835-1.424 2.697.447 5.366 1.059 8.015 1.741 1.723.446 3.437.945 5.14 1.477-12.112-31.655-41.07-52.27-72.687-52.27-31.622 0-60.577 20.615-72.686 52.27a109.044 109.044 0 0 1 5.137-1.477c2.653-.682 5.323-1.294 8.018-1.741-1.289.468-2.567.929-3.84 1.424-1.267.472-2.536.967-3.798 1.477-2.519 1.016-5.016 2.057-7.46 3.207-2.45 1.117-4.857 2.32-7.156 3.663-2.121 1.219-4.157 2.562-5.957 4.071-.075.457-.151.951-.21 1.424a51.768 51.768 0 0 0-.21 2.009 51.354 51.354 0 0 0-.177 4.061 59.216 59.216 0 0 0 .5 8.11c.37 2.692.864 5.366 1.595 7.951.36 1.295.768 2.572 1.24 3.808.237.617.495 1.225.764 1.816.134.294.274.585.413.864l.172.328c.199.101.408.204.607.3l1.204.575c.671.305 1.6.746 2.368 1.09.043-.037.086-.075.123-.114l-2.235-8.513c.474-.13 4.718-1.225 12.032-2.617a38.816 38.816 0 0 1-1.772-.381c-1.665-.414-3.309-.919-4.899-1.564a22.415 22.415 0 0 1-2.309-1.115c-.742-.426-1.472-.908-2.037-1.548 8.036 2.622 24.64 1.434 39.399-.091 13.499-1.391 27.029-2.293 40.63-2.32 13.602.027 27.137.929 40.63 2.32 14.766 1.525 31.37 2.713 39.405.091-.564.64-1.293 1.123-2.035 1.548a22.5 22.5 0 0 1-2.308 1.115c-1.592.645-3.234 1.15-4.899 1.564-.247.059-.496.113-.743.166 8.02 1.488 12.689 2.697 13.188 2.831l-2.138 8.11c.43-.194.864-.381 1.29-.574l1.202-.575c.2-.097.403-.199.607-.3l.166-.328c.146-.279.286-.57.419-.864.27-.591.528-1.199.764-1.816a42.235 42.235 0 0 0 1.241-3.808c.731-2.585 1.225-5.259 1.595-7.951.345-2.685.526-5.398.501-8.11a50.874 50.874 0 0 0-.179-4.059z" id="jkpath14" fill="#f4edae"/><path class="jkst2" d="M116.787 182.661c-1.064.16-2.128.295-3.186.375-.682.033-1.404.102-2.059.102l-.242.005c.822-1.837 1.446-3.26 1.919-4.339.963 1.08 2.188 2.417 3.568 3.857z" id="jkpath16" fill="#e6ccad"/><path class="jkst2" d="M119.101 185.018c3.304 3.272 7.398 5.146 11.904 5.479-7.569 3.074-14.702 4.26-20.197 4.63-5.478.367-11.032-.279-16.474-1.771.456-.082.79-.14 1.193-.189.447-.054 10.206-1.327 14.605-7.868l.413.009 1.08-.009c.731 0 1.395-.06 2.094-.087a43.69 43.69 0 0 0 4.878-.703c.167.171.333.338.504.509z" id="jkpath18" fill="#e6ccad"/><path class="jkst3" d="M128.464 87.071a98.82 98.82 0 0 1-1.048 1.343c-1.933 2.444-4.614 5.57-7.794 8.627a369.585 369.585 0 0 0-11.404-.177c-6.46 0-12.655.171-18.537.457 8.311-3.449 18.296-6.818 29.109-8.842a113.323 113.323 0 0 1 9.674-1.408z" id="jkpath20" fill="#656c67"/><path class="jkst3" d="M79.821 90.792c-2.966 2.084-6.317 4.744-9.566 7.971a360.155 360.155 0 0 0-21.567 2.81c9.207-4.232 19.713-8.127 31.133-10.781z" id="jkpath22" fill="#656c67"/><path class="jkst3" d="M181.48 107.969l-3.384 23.679-16.212 11.355-42.283-4.807-6.365-20.961a1.383 1.383 0 0 0-1.108-.971c-1.567-.253-2.953-.382-4.108-.382-1.16 0-2.541.129-4.115.382-.522.086-.95.461-1.106.971l-6.209 20.45-42.047 9.357-16.662-11.672-3.283-26.572c.715-.404 1.441-.806 2.176-1.209 1.031-.222 2.191-.457 3.475-.704l3.094 25.073c.048.392.264.741.586.967l11.462 8.032a1.425 1.425 0 0 0 1.101.213l34.57-7.692c.119-.027.237-.069.344-.124a1.39 1.39 0 0 0 .682-.827l6.225-20.498c1.67-.43 5.947-1.429 9.706-1.429 3.749 0 8.03.999 9.701 1.429l6.225 20.498c.161.532.624.912 1.176.977l34.57 3.927c.335.037.677-.05.952-.242l11.469-8.025c.31-.22.52-.566.573-.946l3.062-21.421c2.301.444 4.224.846 5.733 1.172z" id="jkpath24" fill="#656c67"/><path class="jkst3" d="M185.751 93.119l-2.976 11.29c-6.086-1.342-19.456-3.975-37.654-5.747 5.946-2.535 12-5.715 17.531-9.69 10.829 1.53 18.78 3.169 23.099 4.147z" id="jkpath26" fill="#656c67"/><g id="jkg32"><path class="jkst4" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath28" fill="#e5caa3"/><path class="jkst4" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath30" fill="#e5caa3"/></g><g id="jkg38"><path class="jkst5" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath34" fill="#c7b39a"/><path class="jkst5" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath36" fill="#c7b39a"/></g><path class="jkst2" d="M187.481 115.502c.508.419.911 1.504.456 6.558-.559 6.188-3.16 17.049-4.771 18.8-1.778.344-5.505-.064-7.778-.595.393-1.559.505-2.306.822-3.9l3.975-2.781c.317-.22.526-.566.58-.941l2.778-19.466c1.686.912 3.421 1.899 3.938 2.325z" id="jkpath40" fill="#e6ccad"/><path class="jkst2" d="M40.937 140.908c.199.704.408 1.407.624 2.1-2.139.628-6.495 1.23-8.465.886-1.633-1.645-4.679-12.966-5.345-18.978-.543-4.871-.162-5.924.333-6.334.575-.483 2.728-1.708 4.593-2.707l2.519 20.449c.048.393.257.741.586.967z" id="jkpath42" fill="#e6ccad"/><path class="jkst2" d="M121.347 141.194l-.151 1.305s-4.581 4.248-11.956 5.199c-7.375.95-13.171-3.582-13.171-3.582.242.788.586 2.567 2.256 4.086a53.184 53.184 0 0 0-6.313-.393c-.804 0-1.616.023-2.401.061-4.539.237-10.924 7.1-15.414 14.014-2.203.697-9.089 2.883-17.06 5.237-7.44-10.309-11.098-20.842-11.469-21.932l.005-.006c-.15-.419-.301-.839-.441-1.268l1.913 1.338v.005l4.726 3.309 1.58 1.101c.236.167.515.253.794.253.102 0 .204-.011.305-.031l43.435-9.67a1.385 1.385 0 0 0 1.025-.95l6.194-20.39c1.069-.145 2.008-.22 2.814-.22.801 0 1.746.075 2.815.22l6.374 20.997c.162.532.624.919 1.171.977z" id="jkpath44" fill="#e6ccad"/><path class="jkst2" d="M170.926 140.066l1.402-.984c-.232.973-.484 1.94-.747 2.896-1.949 6.248-4.25 11.774-6.805 16.656-.565.039-1.161.061-1.8.061-1.972 0-3.986-.167-6.215-.371-3.868-.355-10.007-1.058-11.946-1.283-1.67-1.332-7.385-5.873-12.14-9.615-.187-.151-.348-.291-.505-.42-.837-.708-1.789-1.513-3.717-1.513-1.751 0-4.308.638-10.489 2.508 3.212-2.401 3.233-5.5 3.233-5.5l.151-1.305 40.748 4.629a1.41 1.41 0 0 0 .955-.241l4.094-2.868z" id="jkpath46" fill="#e6ccad"/><path class="jkst6" d="M140.937 54.337c.124 3.625.033 10.194-1.655 16.345a1.335 1.335 0 0 0 0 .704 259.298 259.298 0 0 0-6.446-.591c2.412-5.054 2.938-10.436 3.052-12.332 1.852-1.317 3.696-2.896 5.049-4.126z" id="jkpath48" fill="#ebd599"/><path class="jkst6" d="M79.456 58.462c.112 1.896.638 7.267 3.046 12.317-2.149.171-4.297.37-6.441.596a1.328 1.328 0 0 0 0-.694c-1.686-6.139-1.772-12.714-1.654-16.345 1.353 1.231 3.19 2.81 5.049 4.126z" id="jkpath50" fill="#ebd599"/><path class="jkst7" d="M151.835 125.675c-2.89-1.396-6.059.377-11.828.484-4.292.151-7.896.198-8.132-6.543-.237-6.747 2.513-12.326 6.805-12.478 4.292-.15 8.207 5.2 8.735 11.931.145 1.854-.06 3.207-.521 4.21 3.996-.477 4.899 2.235 4.941 2.396zm-13.488-9.878a2.203 2.203 0 0 0 2.154-2.235 2.186 2.186 0 0 0-2.235-2.153 2.194 2.194 0 0 0 .081 4.388z" id="jkpath52" fill="#2d3136"/><circle transform="rotate(-1.049 138.093 113.428)" class="jkst8" cx="138.307" cy="113.602" id="jkellipse54" r="2.194" fill="#edf6fa"/><path class="jkst7" d="M83.484 120.953c.063 6.747-3.509 6.339-7.806 6.381-.435.011-.848.016-1.258.022-.482.011-.944.016-1.39.005-4.168-.005-6.833-.194-9.19 1.079.058-.145 1.09-2.461 4.835-3.4-.414-.914-.673-2.181-.742-3.937-.257-6.741 3.9-12.269 8.197-12.306 4.292-.042 7.289 5.411 7.354 12.156zm-6.634-3.529a2.195 2.195 0 1 0-.122-4.388 2.195 2.195 0 0 0 .122 4.388z" id="jkpath56" fill="#2d3136"/><circle transform="rotate(-1.473 76.78 115.216)" class="jkst8" cx="76.79" cy="115.23" id="jkellipse58" r="2.195" fill="#edf6fa"/><g class="jkst9" id="jkg64" opacity=".8"><path class="jkst6" d="M50.691 75.155s.667-8.692 2.03-12.023c.702-1.717 4.996-2.81 8.276-3.591 3.278-.78 8.508-2.342 9.524 2.264 1.015 4.606 2.653 7.963 3.746 9.446l-1.404-18.97-22.562 5.464-1.484 16.786.703 1.327 1.171-.703" id="jkpath60" fill="#ebd599"/><path class="jkst6" d="M164.855 75.155s-.666-8.692-2.029-12.023c-.703-1.717-4.997-2.81-8.275-3.591-3.28-.78-8.51-2.342-9.526 2.264-1.013 4.606-2.654 7.963-3.748 9.446l1.407-18.97 22.562 5.464 1.483 16.786-.703 1.327-1.171-.703" id="jkpath62" fill="#ebd599"/></g><path class="jkst10" d="M132.965 18.378s-.598 45.49-11.224 45.49h-14.875-12.752c-10.626 0-11.484-45.47-11.484-45.47l-5.22 15.438.085 21.183 3.707 2.947 1.685 9.096 2.357 5.307 45.482.084 2.105-3.791 1.769-6.4.254-4.043 5.023-14.341z" id="jkpath66" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M166.429 60.794s2.187 15.692 7.974 18.522c5.788 2.829 0 0 0 0l-8.103-2.444z" id="jkpath68" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M48.908 60.794s-2.187 15.692-7.975 18.522c-5.788 2.829 0 0 0 0l8.104-2.444z" id="jkpath70" opacity=".75" fill="#ebd599"/><path class="jkst7" d="M167.987 76.8c2.755.902 5.526 1.858 8.036 3.325-1.343-.532-2.729-.913-4.126-1.257a70.385 70.385 0 0 0-4.201-.924c-2.82-.531-5.65-.982-8.498-1.327-2.841-.37-5.687-.682-8.546-.924-2.858-.241-5.709-.483-8.573-.65-11.446-.704-22.924-.88-34.41-.892-11.483.006-22.962.221-34.409.897-2.862.166-5.715.409-8.572.651-2.857.241-5.71.548-8.546.923-2.847.345-5.678.796-8.498 1.327-1.407.264-2.81.57-4.206.919-1.391.344-2.783.725-4.126 1.257 2.509-1.466 5.28-2.427 8.041-3.331.232-.075.467-.139.703-.214-.015-.059-.032-.113-.043-.177-.048-.317-1.069-7.859.709-18.645.086-.516.456-.935.962-1.075l2.917-.831c.634-22.625 9.952-33.266 10.243-33.594-8.326 13.397-8.25 29.286-8.106 32.986l18.128-5.152c.016-.005.026-.005.042-.01.076-.016.151-.027.226-.032.021 0 .049-.006.075-.006a1.19 1.19 0 0 1 .297.027c.015 0 .031.011.053.016.075.016.145.042.224.075.033.016.054.033.086.049.058.033.119.07.177.112.016.011.034.016.049.033l.032.032c.016.016.037.027.054.044.012.016.494.493 1.262 1.209-.182-5.973.102-23.108 8.262-37.31-.172.498-6.646 19.428-4.415 40.645.724.58 1.486 1.149 2.229 1.649.359.247.58.655.585 1.09.006.07.161 6.833 3.148 12.586.042.086.074.177.102.268 7.429-.505 14.878-.709 22.312-.714 7.436.005 14.88.22 22.307.731.027-.097.06-.193.109-.285 2.986-5.753 3.142-12.516 3.142-12.586.01-.436.231-.843.591-1.09.741-.5 1.493-1.069 2.224-1.649 2.234-21.217-4.24-40.147-4.411-40.645 8.153 14.201 8.444 31.336 8.262 37.31a62.536 62.536 0 0 0 1.261-1.209c.016-.016.039-.027.053-.044.012-.01.018-.021.033-.032.016-.016.033-.022.049-.033.06-.042.119-.079.177-.118.028-.01.054-.027.081-.043.081-.033.155-.059.236-.08.016 0 .033-.011.049-.011.096-.021.2-.032.296-.027.027 0 .049.006.07.006.075.005.156.016.231.032.012.006.028.006.042.01l18.129 5.152c.146-3.7.221-19.59-8.104-32.986.289.328 9.609 10.969 10.237 33.594l2.922.831c.499.14.875.559.962 1.075 1.777 10.786.752 18.328.708 18.645-.01.065-.026.124-.042.182.239.07.47.139.707.215zm-3.297-.968c.14-1.207.789-7.809-.591-16.801l-20.52-5.833c.184 3.475.265 11.012-1.707 18.199a1.619 1.619 0 0 1-.101.258c.203.021.408.037.606.064 5.769.661 11.511 1.584 17.189 2.83 1.712.398 3.426.823 5.124 1.283zm-25.409-5.151c1.688-6.15 1.779-12.72 1.655-16.345-1.353 1.23-3.197 2.809-5.049 4.125-.114 1.896-.64 7.278-3.052 12.332 2.149.173 4.298.366 6.446.591a1.33 1.33 0 0 1 0-.703zm-56.78.098c-2.408-5.05-2.934-10.422-3.046-12.317-1.858-1.316-3.696-2.895-5.049-4.125-.119 3.631-.032 10.206 1.654 16.345.065.237.058.473 0 .694 2.145-.227 4.292-.425 6.441-.597zm-8.933.864a1.65 1.65 0 0 1-.098-.247c-1.975-7.187-1.889-14.723-1.712-18.199L51.244 59.03c-1.38 8.982-.736 15.583-.597 16.797 1.703-.462 3.411-.887 5.131-1.284 2.835-.628 5.693-1.154 8.556-1.638 2.869-.478 5.747-.843 8.626-1.192.205-.027.404-.042.608-.07z" id="jkpath72" fill="#2d3136"/><g id="jkXMLID_1_"><g id="jkg78"><path class="jkst7" d="M129.293 18.973v17.025h-12.068v-4.974h-2.72v22.981h4.109v12.85H97.505v-12.85h4.092v-22.98h-2.711v4.974h-12.06V18.973zm-3.626 13.408v-9.789H90.443v9.789h4.816v-4.974h9.964v30.225h-4.1v5.606h13.865v-5.606h-4.1V27.407h9.964v4.974z" id="jkpath74" fill="#2d3136"/><path class="jkst0" id="jkpolygon76" fill="#cb3349" d="M101.123 57.632h4.1V27.407h-9.964v4.974h-4.816v-9.79h35.224v9.79h-4.816v-4.974h-9.964v30.225h4.1v5.606h-13.864z"/></g></g><path class="jkst3" d="M30.694 93.119c1.759-.399 4.136-.907 7.051-1.47a104.37 104.37 0 0 0-6.222 4.597z" id="jkpath83" fill="#656c67"/><path class="jkst5" d="M95.111 139.78s.492 3.165-3.938 4.519c-4.428 1.355-32.482 9.716-35.682 9.263-3.199-.451-11.319-5.874-11.319-5.874l-1.969-7.004 12.016 7.492z" id="jkpath85" fill="#c7b39a"/><path class="jkst5" d="M120.242 139.167s-.354 3.182 4.131 4.345c4.484 1.161 32.875 8.295 36.05 7.704 3.176-.591 11.053-6.361 11.053-6.361l1.663-7.084-11.045 6.588z" id="jkpath87" fill="#c7b39a"/><path class="jkst5" d="M28.412 133.956s3.887 7.775 10.166 5.083l4.485 1.645-.448 3.29-9.419 1.195-2.541-1.494z" id="jkpath89" fill="#c7b39a"/><path class="jkst5" d="M187.551 131.822s-6.353 8.115-12.632 5.424l-2.019 1.302.448 3.289 9.419 1.196 2.54-1.495z" id="jkpath91" fill="#c7b39a"/><path class="jkst5" d="M89.279 192.904s23.03 11.611 49.106-4.188l-8.374-.571s-18.272 7.232-32.738 3.235z" id="jkpath93" fill="#c7b39a"/><path class="jkst7" d="M112.626 171.509l1.594 1.899c.036.046 3.577 4.26 7.906 8.552 2.879 2.853 6.357 4.297 10.343 4.297 1.361 0 2.791-.175 4.235-.523 1.34-.326 2.796-.673 4.287-1.03 5.384-1.287 11.482-2.749 14.438-3.577.585-.166 1.238-.315 1.925-.472 3.935-.909 9.329-2.163 12.187-7.889 2.149-4.297 5.047-9.874 7.197-13.961-1.863.859-3.816 1.79-5.203 2.52-2.138 1.123-4.938 1.667-8.558 1.667-2.152 0-4.266-.181-6.605-.389-4.675-.43-12.586-1.361-12.667-1.372l-.606-.067-.478-.383c-.071-.052-7.003-5.575-12.606-9.981-.227-.186-.434-.358-.621-.513-.59-.503-.59-.503-.942-.503-1.797 0-7.02 1.62-18.462 5.167l-.703.223-.689-.26c-.078-.026-7.585-2.81-16.581-2.81-.736 0-1.47.019-2.185.056-.901.046-5.958 2.448-12.425 12.68l-.419.657-.741.238c-.107.037-11.238 3.63-23.042 7.005l-.766.218-.725-.337c-.077-.031-4.696-2.174-9.091-4.194 2.397 3.541 5.462 7.958 8.159 11.422 4.711 6.067 10.649 11.674 22.034 11.674 1.428 0 2.945-.088 4.503-.265 11.581-1.309 14.563-1.837 16.168-2.117.543-.092.973-.171 1.522-.238.088-.011 9.571-1.237 12.232-7.206 2.744-6.134 3.298-7.595 3.319-7.651l.968-2.583s.12-.669.317-.877c0 .005 0 .005.005.005l.019.016c.305.219.757.902.757.902zM40.499 55.71c-2.516 1.014-5.016 2.06-7.46 3.209-2.449 1.119-4.856 2.32-7.155 3.66-2.121 1.222-4.157 2.563-5.954 4.076-.077.455-.149.952-.211 1.423a51.357 51.357 0 0 0-.388 6.068c-.026 2.713.16 5.426.502 8.112.372 2.692.864 5.369 1.594 7.952a41.963 41.963 0 0 0 1.243 3.804c.233.623.492 1.228.762 1.818.134.294.274.585.413.864l.172.326c.201.104.409.207.605.3l1.206.574c.673.311 1.6.751 2.366 1.093.046-.037.088-.078.124-.114l-2.231-8.511c.471-.129 4.717-1.227 12.032-2.619a33.744 33.744 0 0 1-1.775-.379 36.704 36.704 0 0 1-4.898-1.563 22.857 22.857 0 0 1-2.309-1.119c-.741-.425-1.471-.905-2.035-1.547 8.035 2.624 24.637 1.433 39.398-.088 13.501-1.393 27.028-2.293 40.628-2.325 13.6.031 27.138.931 40.63 2.325 14.77 1.522 31.374 2.713 39.406.088-.564.642-1.293 1.122-2.034 1.547-.739.42-1.522.782-2.309 1.119a36.965 36.965 0 0 1-4.903 1.563c-.244.056-.492.114-.741.166 8.02 1.486 12.689 2.697 13.186 2.832l-2.138 8.107c.43-.192.864-.377 1.288-.574l1.207-.574c.196-.094.404-.196.606-.3l.166-.326c.144-.279.284-.57.419-.864.27-.591.528-1.196.767-1.818.471-1.231.879-2.51 1.236-3.804.731-2.583 1.228-5.26 1.595-7.952.346-2.686.528-5.4.502-8.112a52.755 52.755 0 0 0-.176-4.059 51.573 51.573 0 0 0-.213-2.009 29.83 29.83 0 0 0-.213-1.423c-1.797-1.513-3.831-2.853-5.954-4.076-2.299-1.34-4.704-2.541-7.159-3.66-2.438-1.149-4.943-2.195-7.46-3.209a140.105 140.105 0 0 0-3.801-1.476c-1.267-.491-2.552-.956-3.835-1.423 2.696.445 5.369 1.06 8.013 1.739 1.724.446 3.444.948 5.141 1.481-12.11-31.658-41.07-52.272-72.685-52.272-31.622 0-60.576 20.614-72.684 52.272a107.832 107.832 0 0 1 5.135-1.481c2.651-.678 5.322-1.294 8.02-1.739-1.29.466-2.568.931-3.842 1.423-1.268.47-2.535.967-3.799 1.475zm159.43 18.316a53.972 53.972 0 0 1-.258 8.733 55.462 55.462 0 0 1-1.619 8.605c-.4 1.414-.86 2.811-1.404 4.198a38.295 38.295 0 0 1-.89 2.071c-.161.341-.331.678-.523 1.025l-.284.512a8.975 8.975 0 0 1-.348.574l-.294.457-.461.237c-.492.254-.895.445-1.342.653l-1.298.585a88.22 88.22 0 0 1-2.62 1.065c-.611.239-1.15.457-1.662.674l-1.444 5.487c-.036-.009-.471-.12-1.283-.315l-.078.574c1.594.833 4.726 2.522 5.793 3.403 2.148 1.775 2.299 4.587 1.823 9.841-.244 2.697-1.139 7.946-2.381 12.767-2.144 8.298-3.283 9.273-4.753 9.649-.746.192-1.894.383-3.008.383-2.266 0-5.353.063-7.429-.439-.533 1.888-2.055 6.812-5.068 12.962.151-.073.3-.135.435-.207 3.717-1.952 10.861-5.064 11.162-5.199l5.643-2.452-2.89 5.435c-.067.118-6.264 11.773-10.059 19.383-3.769 7.538-10.835 9.179-15.065 10.151-.637.151-1.241.291-1.733.425-3.035.854-9.18 2.319-14.599 3.623-.064.016-.13.033-.197.042a64.057 64.057 0 0 1-10.955 5.411c-14.568 5.518-29.923 5.208-43.844.092a647.05 647.05 0 0 1-9.193 1.097 45.12 45.12 0 0 1-4.985.291c-13.264 0-20.294-6.736-25.425-13.331-5.493-7.062-12.212-17.546-12.497-17.985L31 158.426l6.585 2.961c3.152 1.419 12.524 5.757 15.205 7 .217-.061.43-.124.642-.186-4.457-6.357-8.112-13.605-10.695-21.634-2.195.662-5.576 1.175-8.206 1.175-.961 0-1.822-.072-2.484-.228-1.471-.336-3.148-1.754-5.431-9.795-1.325-4.668-2.314-9.764-2.603-12.387-.57-5.121-.466-7.864 1.662-9.636 1.283-1.071 5.611-3.344 6.507-3.809l-.192-1.58c-13.75 8.08-21.991 15.22-22.157 15.366L0 134.302l7.005-11.047c5.544-8.755 11.948-15.832 17.84-21.284-.244-.098-.471-.196-.71-.294l-1.299-.585a34.907 34.907 0 0 1-1.34-.653l-.461-.237-.295-.457c-.166-.249-.238-.388-.347-.574l-.29-.512c-.181-.347-.358-.684-.518-1.025a30.878 30.878 0 0 1-.89-2.071 44.74 44.74 0 0 1-1.404-4.198 54.745 54.745 0 0 1-1.62-8.605 54.664 54.664 0 0 1-.259-8.733c.078-1.455.218-2.909.419-4.354.104-.725.213-1.45.358-2.17.15-.734.296-1.418.518-2.221l.155-.564.404-.317c2.294-1.802 4.768-3.163 7.284-4.369a78.87 78.87 0 0 1 6.311-2.616c5.943-16.493 16.162-31.118 29.591-41.311C74.337 5.57 90.664 0 107.671 0s33.334 5.57 47.218 16.106c13.43 10.193 23.649 24.819 29.588 41.307a78.282 78.282 0 0 1 6.316 2.62c2.515 1.206 4.99 2.567 7.283 4.369l.404.317.156.564c.227.803.372 1.487.517 2.221.146.72.26 1.445.357 2.17.203 1.443.348 2.897.419 4.352zm-11.995 48.031c.456-5.052.058-6.139-.455-6.554-.513-.43-2.247-1.412-3.935-2.329l-2.779 19.464a1.39 1.39 0 0 1-.58.942l-3.977 2.781c-.315 1.593-.429 2.345-.817 3.903 2.273.528 5.999.938 7.775.595 1.612-1.748 4.214-12.61 4.768-18.802zm-5.161-17.648l2.977-11.29c-4.318-.978-12.27-2.615-23.1-4.148-5.53 3.976-11.582 7.155-17.53 9.691 18.199 1.771 31.57 4.406 37.653 5.747zm-4.68 27.237l3.385-23.676a240.127 240.127 0 0 0-5.731-1.169l-3.059 21.422a1.415 1.415 0 0 1-.575.943l-11.472 8.023c-.27.192-.616.28-.947.243l-34.572-3.929a1.391 1.391 0 0 1-1.176-.973l-6.227-20.5c-1.668-.431-5.949-1.43-9.696-1.43-3.764 0-8.041.999-9.708 1.43l-6.228 20.5a1.388 1.388 0 0 1-1.025.947l-34.572 7.692a1.483 1.483 0 0 1-.306.033 1.36 1.36 0 0 1-.792-.25l-11.467-8.029a1.396 1.396 0 0 1-.585-.968l-3.091-25.072c-1.284.249-2.443.487-3.479.703-.734.405-1.46.809-2.174 1.213l3.281 26.568 16.666 11.675 42.047-9.354 6.207-20.449a1.389 1.389 0 0 1 1.108-.975c1.574-.253 2.95-.382 4.116-.382 1.153 0 2.536.129 4.105.382.528.083.957.461 1.108.975l6.366 20.956 42.282 4.808zm-8.07-4.411l2.992-20.948c-8.439-1.536-20.78-3.394-35.897-4.554-13.647 4.707-25.077 6.108-25.766 6.155l-.797.057c4.353.374 8.454 1.544 8.66 1.605.452.135.804.481.944.933l6.186 20.366 33.138 3.764zm2.303 11.845l-1.404.983-3.779 2.651-4.095 2.868c-.279.192-.621.28-.954.243l-40.746-4.633-2.966-.337a1.39 1.39 0 0 1-1.171-.977l-6.377-20.998c-1.066-.145-2.014-.219-2.81-.219-.809 0-1.751.073-2.817.219l-6.192 20.392a1.383 1.383 0 0 1-1.025.946l-43.435 9.672c-.103.02-.206.03-.305.03-.279 0-.559-.083-.798-.253l-1.578-1.098-4.726-3.307v-.011l-1.91-1.335c.135.43.289.85.441 1.268l-.006.006c.368 1.092 4.028 11.622 11.467 21.929a873.96 873.96 0 0 0 17.057-5.234c4.488-6.917 10.877-13.777 15.418-14.014a51.12 51.12 0 0 1 2.402-.061c2.221 0 4.344.16 6.31.393-1.671-1.517-2.013-3.298-2.256-4.085 0 0 5.793 4.53 13.17 3.584 7.378-.953 11.959-5.204 11.959-5.204s-.021 3.102-3.236 5.503c6.182-1.869 8.739-2.511 10.489-2.511 1.931 0 2.883.808 3.717 1.519.161.129.322.268.507.419a3519.302 3519.302 0 0 1 12.141 9.614c1.936.227 8.075.926 11.943 1.283 2.23.201 4.245.372 6.217.372.637 0 1.233-.026 1.797-.063 2.558-4.88 4.857-10.411 6.808-16.653.261-.96.516-1.928.743-2.901zm-15.034-51.593c-.01-.006-.02-.012-.031-.012a551.624 551.624 0 0 0-9.826-.651 905.6 905.6 0 0 0-13.667-.668 72.95 72.95 0 0 1-1.574 2.225c-2.479 3.355-7.398 9.51-13.704 14.729 8.926-1.6 24.409-5.56 37.803-14.905.336-.238.668-.486.999-.718zm-29.876.926c.377-.471.729-.926 1.044-1.34-3.281.331-6.512.808-9.67 1.408-10.814 2.024-20.801 5.389-29.11 8.837a383.259 383.259 0 0 1 18.54-.455c3.908 0 7.708.067 11.404.176 3.179-3.056 5.861-6.182 7.792-8.626zm3.587 102.085c-4.503-.332-8.598-2.205-11.903-5.477a271.86 271.86 0 0 0-.502-.512 44.25 44.25 0 0 1-4.881.704c-.698.026-1.361.087-2.091.087l-1.083.011-.413-.011c-4.396 6.539-14.159 7.813-14.605 7.87-.403.046-.734.103-1.191.186 5.442 1.491 10.996 2.138 16.474 1.77 5.492-.367 12.627-1.558 20.195-4.628zm-17.4-7.461a45.604 45.604 0 0 0 3.184-.378 138.958 138.958 0 0 1-3.568-3.857 398.441 398.441 0 0 1-1.92 4.339h.243c.658.001 1.378-.071 2.061-.104zm-3.354-78.632c1.827-1.103 3.582-2.366 5.249-3.712a422.33 422.33 0 0 0-7.278-.072c-10.137 0-19.606.415-28.189 1.061-8.61 4.209-13.875 7.672-13.998 7.76l-8.268 5.514 5.679-8.149a52.452 52.452 0 0 1 2.956-3.857c-9.536 1.066-17.477 2.329-23.41 3.422l3.038 24.632 10.453 7.321 33.184-7.378 6.212-20.464c.104-.337.331-.621.627-.793.098-.063.202-.109.315-.14.192-.052 3.51-.999 7.336-1.465zm3.816-18.788c-2.31-.036-4.623-.057-6.933-.062h-.005c-3.39.005-6.787.041-10.189.109l-6.269 2.971c-.005.005-.041.021-.088.048-.942.46-9.174 4.613-16.919 12.021 6.943-3.65 17.146-8.418 29.153-12.115a144.186 144.186 0 0 1 11.25-2.972zM70.251 98.761c3.251-3.225 6.605-5.886 9.567-7.967-11.415 2.651-21.923 6.543-31.128 10.778a360.846 360.846 0 0 1 21.561-2.811zm2.159-9.949a150.122 150.122 0 0 1 11.813-2.796c-5.798.212-11.6.481-17.393.808-3.366.186-6.715.414-10.065.667-1.678.129-3.345.263-5.007.445-.476.046-.942.098-1.418.16-4.369 2.614-21.127 13.134-32.631 26.889 11.179-7.769 30.654-19.443 54.701-26.173zm-30.85 54.197a68.861 68.861 0 0 1-.621-2.102l-5.162-3.612a1.391 1.391 0 0 1-.586-.969l-2.516-20.449c-1.864.999-4.017 2.225-4.592 2.707-.497.409-.875 1.46-.336 6.332.668 6.01 3.712 17.333 5.348 18.979 1.968.347 6.327-.258 8.465-.886zm-3.815-51.36a229.005 229.005 0 0 0-7.051 1.47l.829 3.127a103.93 103.93 0 0 1 6.222-4.597z" id="jkpath95" fill="#2d3136"/></g></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tune" xmlns="http://www.w3.org/2000/svg"><path d="M6.85 2.852h-2v6h2v-6m12 0h-2v10h2v-10m-16 10h2v8h2v-8h2v-2h-6v2m12-6h-2v-4h-2v4h-2v2h6v-2m-4 14h2v-10h-2v10m4-6v2h2v4h2v-4h2v-2h-6z" fill="#fbc02d" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 50 50" id="twig" xmlns="http://www.w3.org/2000/svg"><path d="M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537.132.27.282.487.332.482.123-.011.075-1.196-.1-2.454-.331-2.398-1.176-4.435-2.358-5.69-.2-.212-.344-.4-.319-.419.093-.067 1.327.843 1.842 1.359.293.293.735.825.981 1.181.328.474.465.618.51.534.078-.147-.21-9.903-.376-12.701-.074-1.255.063-1.023.61 1.035 1.064 4.006 1.858 7.922 2.342 11.55.086.637.173 1.172.195 1.19.022.016.092.001.157-.034.888-.483 1.524-.667 2.55-.736.727-.048.945.062.35.178-1.15.222-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.434.152.366.037.426-1.067.56-.716.088-.977.096-1.202.037-.356-.092-1.118-.098-1.195-.008-.031.036-.243.066-.47.066-.38 0-.423-.017-.535-.215zm1.974-3.233c.152-.205.072-.41-.204-.522-.225-.09-.263-.088-.437.025-.21.137-.252.43-.08.554.18.13.607.096.72-.057zm1.248.086a.763.763 0 0 0 .214-.203c.241-.33-.352-.622-.745-.366-.406.265.08.785.531.569zm2.288 3.094c-.033-.039.117-.387.334-.775.216-.387.411-.665.433-.618.07.152-.201 1.28-.33 1.372-.15.108-.354.117-.437.02zM8.2 47.092c-.29-.343-.221-.434.14-.182.176.123.321.263.321.31 0 .165-.279.087-.46-.128zm8.649-.145c0-.053.102-.18.227-.282.25-.204.312-.113.143.207-.095.18-.37.236-.37.075zm8.065-.827c-.243-.025-.48-.088-.527-.141-.11-.125-.114-3.043-.004-3.043.045 0 .132.149.193.331.127.38.228.42.31.124.094-.337.065-3.472-.039-4.297-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.436-4.047-1.711-.764-.15-.522-.224.598-.182 2.364.089 4.167.706 5.847 2.001a11.046 11.046 0 0 1 2.32 2.502c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.718 2.539-.988 4.02-.952.933.023 1.01.036 1.77.307a6.822 6.822 0 0 1 1.363.662c.612.407 1.309 1.004 1.235 1.058-.026.018-.343-.165-.705-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.651 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.449 1.612-.904 1.715-.83.022.016-.172.22-.432.454-1.957 1.754-3.248 3.76-4.232 6.572-.938 2.68-1.366 5.588-1.368 9.3-.002 1.741.188 4.385.366 5.101.125.505.08.546-.585.546-.55 0-2.306.138-3.416.27-.414.05-.817.04-1.609-.036-.58-.056-1.129-.119-1.218-.14-.165-.037-.18-.014-.2.302-.01.186-.098.203-.728.139zm2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.211.13.51.42.616.297.108.413.106.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.691-1.755-.307-1.428.404.121.266.299.35.738.354.227 0 .387-.045.516-.147zm3.011 6.681c-.027-.05.088-.268.256-.484.879-1.135 1.22-1.544 1.284-1.544.04 0 .056.037.036.082l-.423.964c-.212.485-.445.924-.519.977-.169.122-.57.125-.634.005zm2.446-.596c0-.121.853-.683.896-.59.018.04-.056.209-.166.376-.168.259-.238.305-.464.305-.164 0-.266-.035-.266-.091zm-13.04-.124c-.177-.159-.493-.656-.462-.725.018-.038.248.1.512.309.264.207.457.405.428.438-.075.088-.371.074-.478-.022z" fill="#9bb92f" stroke-width=".078"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript" xmlns="http://www.w3.org/2000/svg"><path d="M49 51h408v408H49V51zm246.669 314.879l19.463-1.702c.922 7.8 3.067 14.199 6.435 19.198 3.368 4.998 8.597 9.04 15.688 12.124 7.09 3.085 15.067 4.627 23.93 4.627 7.87 0 14.819-1.17 20.845-3.51 6.027-2.34 10.512-5.548 13.455-9.625 2.942-4.077 4.413-8.526 4.413-13.348 0-4.892-1.418-9.164-4.254-12.816-2.836-3.651-7.516-6.718-14.039-9.2-4.183-1.63-13.436-4.165-27.759-7.604s-24.355-6.683-30.099-9.732c-7.445-3.899-12.993-8.739-16.644-14.517-3.652-5.779-5.478-12.249-5.478-19.41 0-7.871 2.234-15.227 6.701-22.069 4.467-6.842 10.99-12.036 19.569-15.581 8.58-3.546 18.116-5.318 28.61-5.318 11.557 0 21.75 1.861 30.577 5.584 8.828 3.722 15.617 9.199 20.368 16.432 4.75 7.232 7.303 15.421 7.657 24.568l-19.782 1.489c-1.064-9.856-4.662-17.301-10.795-22.335-6.133-5.034-15.191-7.551-27.174-7.551-12.479 0-21.573 2.286-27.281 6.86-5.707 4.573-8.561 10.086-8.561 16.538 0 5.602 2.021 10.21 6.062 13.826 3.971 3.617 14.34 7.321 31.109 11.115 16.769 3.793 28.273 7.108 34.513 9.944 9.076 4.183 15.776 9.483 20.101 15.9 4.325 6.417 6.488 13.809 6.488 22.175 0 8.296-2.375 16.113-7.126 23.452-4.751 7.338-11.575 13.046-20.474 17.123-8.898 4.077-18.913 6.116-30.045 6.116-14.11 0-25.933-2.056-35.47-6.169-9.537-4.112-17.017-10.299-22.441-18.559-5.424-8.26-8.278-17.602-8.562-28.025zm-65.728 50.094V278.454h51.583v-18.399H157.938v18.399h51.37v137.519h20.633z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript-def" xmlns="http://www.w3.org/2000/svg"><path d="M457 459H49V51h408v408zM69 71v368h368V71H69z" fill="#0288d1"/><text x="342.219" y="344.544" font-family="ArialMT" font-size="12" fill="#0288d1" transform="translate(-6058.94 -5838) scale(18.1514)"><tspan style="-inkscape-font-specification:sans-serif" font-family="sans-serif" font-weight="400">TS</tspan></text></symbol><symbol viewBox="0 0 24 24" id="url" xmlns="http://www.w3.org/2000/svg"><path d="M16 6h-3v1.9h3a4.1 4.1 0 0 1 4.1 4.1 4.1 4.1 0 0 1-4.1 4.1h-3V18h3a6 6 0 0 0 6-6c0-3.32-2.69-6-6-6M3.9 12A4.1 4.1 0 0 1 8 7.9h3V6H8a6 6 0 0 0-6 6 6 6 0 0 0 6 6h3v-1.9H8c-2.26 0-4.1-1.84-4.1-4.1M8 13h8v-2H8v2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="verilog" xmlns="http://www.w3.org/2000/svg"><path d="M17.282 17.08H6.718V6.513h10.564m4.226 4.226V8.627h-2.113V6.514c0-1.173-.95-2.113-2.113-2.113H15.17V2.288h-2.113v2.113h-2.112V2.288H8.83v2.113H6.718c-1.173 0-2.113.94-2.113 2.113v2.113H2.492v2.113h2.113v2.113H2.492v2.113h2.113v2.113a2.113 2.113 0 0 0 2.113 2.113H8.83v2.113h2.113v-2.113h2.112v2.113h2.113v-2.113h2.113a2.113 2.113 0 0 0 2.113-2.113v-2.113h2.113v-2.113h-2.113V10.74m-6.339 2.113h-2.112V10.74h2.112m2.113-2.113H8.831v6.34h6.338z" fill="#ff7043" stroke-width="1.056"/></symbol><symbol viewBox="0 0 24 23.999999" id="vfl" xmlns="http://www.w3.org/2000/svg"><defs><style>.jra{fill:#f05223}.jrb{fill:url(#jra)}</style><radialGradient id="jra" cx="205.45" cy="208.29" r="225.35" gradientTransform="matrix(.04556 0 0 .0456 2.888 2.88)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffd104" offset="0"/><stop stop-color="#faa60e" offset=".35"/><stop stop-color="#f05023" offset="1"/></radialGradient></defs><title>houdinibadge</title><g stroke-width=".046"><path class="jra" d="M19.97 3H4.03A1.03 1.031 0 0 0 3 4.031v4.135C4.548 6.977 6.563 6.21 8.948 6.21c5.107.003 8.35 3.574 8.348 8.081 0 3.13-1.46 5.485-3.746 6.71h6.42A1.03 1.031 0 0 0 21 19.968V4.031a1.03 1.031 0 0 0-1.03-1.03z" fill="#f4511e"/><path class="jrb" d="M3 17.722v2.247A1.03 1.031 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722z" fill="url(#jra)"/><path class="jra" d="M8.948 8.231c-2.586-.09-4.598.86-5.948 2.264v3.163c.918-2.654 3.447-3.87 5.565-3.85 2.647.027 4.689 2.025 4.7 4.284.012 2.159-.892 3.748-3.33 4.14-1.33.213-3.411-.567-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.905 1.213.293 2.102 1.139 1.921 1.048-.224 1.475-1.156 1.475-1.878 0-.762-.718-1.994-2.498-1.951-2.204.052-3.591 1.639-3.638 3.602-.056 2.468 2.253 4.091 4.622 4.121 3.48.046 5.543-2.24 5.539-5.586-.005-3.029-2.434-5.946-6.085-6.072z" fill="#f05223"/></g></symbol><symbol viewBox="0 0 24 24" id="virtual" xmlns="http://www.w3.org/2000/svg"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h7l-2 3v1h8v-1l-2-3h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 281.25 281.25" id="visualstudio" xmlns="http://www.w3.org/2000/svg"><path d="M196.18 101.74l-52.778 42.444 52.778 40.889V101.74m-136.67 110l-30-18.889v-100L62.843 81.74l47.778 37 96.666-89.222 44.444 27.778v172.22l-55.555 22.222-85.111-81.555-51.555 41.555m3.333-48.889l20.667-19.111-20.667-19.778z" fill="#ab47bc" stroke-width="11.111"/></symbol><symbol viewBox="0 0 300 300" id="vscode" xmlns="http://www.w3.org/2000/svg"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0}.icon-white{fill:#fff}</style></defs><title>BrandVisualStudioCode</title><path d="M218.62 29.953l-105.41 96.92L54.301 82.47 29.955 96.64l58.068 53.359-58.068 53.359 24.346 14.212 58.909-44.402 105.41 96.878 51.424-24.976V54.93zm0 63.744v112.6l-74.719-56.302z" fill="#2196f3" stroke-width="17.15"/></symbol><symbol viewBox="0 0 24 24" id="vue" xmlns="http://www.w3.org/2000/svg"><path d="M1.821 4.15l10.21 17.618L22.24 4.235V4.15h-7.692L12.113 8.33 9.691 4.15H1.82z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179H5.937z" fill="#35495e"/></symbol><symbol viewBox="0 0 420 419" id="watchman" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linecap="round" stroke-linejoin="bevel"><path d="M166.95 145.32a93.935 123.23 0 0 1 92.934 3.263" fill="none" stroke-width="18.467"/><path d="M162.92 137.96L44.63 256.25a174.07 173.93 0 0 0 5.705 16.486l123.68-123.68-11.096-11.096zM266.54 144.04l-11.096 11.096 117.16 117.16a174.07 173.93 0 0 0 5.691-16.5l-111.76-111.76zm170.65 170.65v22.193l17.1 17.1 11.096-11.098-28.195-28.195z" fill="#fff" stroke-width="1.963"/><path d="M167.52 273.36a93.935 123.23 0 0 1 92.934-3.263" fill="none" stroke-width="18.467"/><path d="M49.516 144.56a174.07 173.93 0 0 0-.809 2.213 174.07 173.93 0 0 0-4.757 14.344 174.07 173.93 0 0 0-.016.055l119.56 119.56 11.098-11.096-125.07-125.07zM454.87 64.703l-17.668 17.668v22.191l28.764-28.764-11.096-11.096zm-80.984 80.984l-117.86 117.86 11.098 11.096 112.18-112.18a174.07 173.93 0 0 0-5.416-16.777z" fill="#fff" stroke-width="1.963"/></g><image x="21.229" y="20.262" width="378" height="377.1" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAGjCAYAAABjSWGNAAAgAElEQVR4AeydB3hUVdrH/+fOpNMF JAFUmivFXtZuIBRRQUUTil1RV3et6Lr6rSu6rg3B1dXVtXeBCCioCASIimLDhkFsgAIJSAkhPZmZ 8z3vjReGMJlMueeWmfc88MzMLaf8zp3855zznvcV4MQEkoxA7sVr01PrUrv4pdZNBNA1AHQRkJ0g ZAcJrYOA7Aipv8/Q6JgUKdBkG0iZISDSDVwSaAfAY3z+/bVSAD56L+mfwA5INAqgSkrUCKA2IOQO IcQOIUU5pNwhNZQLia2Q2OyH3OJBxqaiwk4VzfLlj0wg4QmIhG8hNzDJCEgxJH9DjgfevgGBAyCx vxSyh5DoAWA/CHQH0MEFUOoBbJbAeiHEBiED6wMSv0DgF02In1J2Vq+ZP78fXcOJCSQMARakhOnK 5GrICaO3tE3P8B0kERgkgP6Qop8E+gqgL4Bdo5gEphKAwHpIrIHAjxL4DpCrAo1yVfHsHhsSuN3c tAQmwIKUwJ2bGE2TYsjY33qLQOBwCHk4JA6HhgE08kmM9ilpRQUgvwPE1xD4MiC0r2R67dfFz/eq U1IaZ8oETCLAgmQSSM7GHAIj8td38sN7HAROkpDHATgMTWs15hSQvLn4JLBaQHwqEPgQQiwvmpHz XfLi4JY7kQALkhN7JYnqlDd2w4GQnuMk5AkCOBHAQQD4ubTgGZDAFg1ieQCBj6Dhg7SKmhW8LmUB eC6iRQL8xW8RDZ8wn4AUQ8eVHib9YjCELj4nAOhqfjmcY4wE6iGxAkIsCwDvSel5v7iwa1WMefFt TCBqAixIUSPjG6IhkDtmQw/N4xkKTQ4TEkNZgKKhZ/u1DZD4CAKLIMSik/p3WzF5sgjYXiuuQMIS YEFK2K61p2GTJ0tt2Xebj5EyMArAab+vAdlTGS7VVAI0xSeAd6QQb2uBtIW8V8pUvJwZz9XzM2AG AdpoKqpTh3uEdlYA8gwBdDEjX87D0QQaALlMCLzha5Rz2NTc0X3lmsrxCMk1XeWsig6/YFOWr0Ge JoBzICWNhNo6q4ZcGwsJkFOKT4TUZiOgzSqate8aC8vmohKIAAtSAnWm6qaMHPljWl2bzBGapk2A lKMBZKguk/N3JYHPpMCrHuGbuWj6fqWubAFX2hYCLEi2YHdToVIMKSg7SQAXABgDoJObas91tZWA H5DvCeCV2rrUwg/ndqm0tTZcuOMJsCA5vovsqeDwszd19af4LwbEZQAOtKcWXGoCEaiCwAwB7emi Gd0+TqB2cVNMJMCCZCJMt2dFFnIflJQNh8BEgKbkRIrb28T1dySBlULIpzyBwCsLCntud2QNuVK2 EGBBsgW7swqlvUIer+cSQE7UPWI7q3pcm8QlUAtgtibFU4sKu70PCJm4TeWWRUKABSkSSgl5DTkt 3XSqJuVfJDAiRFyfhGw1N8qxBH4AxJP+hrpnit/otcOxteSKKSXAgqQUr/My18216wMXCOB6AH9w Xg25RklOoEoI8bzw+R5ZNKvnj0nOIumaz4KUJF0+YsyWbL/Xdx0gr5BAxyRpNjfTvQQCkPIdDdqD iwqz33NvM7jm0RBgQYqGlguvbfKmrd2MJrPtNBc2gauc5ASElJ8EIO4/eWD2m+xLL7EfBhakBO3f wfmlR2oCtwI4G4CWoM3kZiURASmwGpAPVLTPeXnFk6IxiZqeNE1lQUqwrs4bW3Y0AoF/QIjT2Vdh gnUuN8cgsFYK3LNPoPzFwsKBDcZBfnU/ARYk9/eh3oIh+WXHaELeIZs8bCdIq7gZTCAsgXUQuK9T oPw5FqawnFxzkgXJNV0VuqJD8zcdDCHvlvpG1tDX8FEmkOAE1gohJp/Yv9vLvMbk7p5mQXJp/w0Z u7mPkP7JACbwGpFLO5GrbS4BiRII/H3xzOw3eZOtuWityo0FySrSJpWTO760s8cv7wQEeVVINSlb zoYJJBAB+bGQ4uaiwpxlCdSopGgKC5JLuvm4/PUZmcJ7NSD/DqCDS6rN1WQCdhKY45f+vxYX9vzJ zkpw2ZETYEGKnJVNV1L4h9JxAuI+9jNnUxdwsW4mQFZ4//E31N/NLomc340sSA7uo2H5Gw7zC+3f AjjFwdXkqjEBNxDYKgVuPbl/9rNs+ODc7mJBcmDf5J61toMnNfVfgLgCgNeBVeQqMQF3EpD4XGja NRyTyZndx4LkqH6RYkj+pouEkFMAdHZU1bgyTCBxCEgIPFvvabx52av7lydOs9zfEhYkh/Th0HM2 95Ye//8ADHVIlbgaTCDRCWwWENcWzcyemegNdUv7WJBs7qncXOnVum66XoBMuZFpc3W4eCaQdAQE xFyfz//n4tk9NiRd4x3WYBYkGzuEjBak0J6WwJE2VoOLZgJMANgJIW49qX+3J9jowb7HgQXJBva0 pyhLeO+QkJPYaMGGDuAimUDLBJYJgSuKZuR81/IlfEYVARYkVWRbyHfouLJcBPCUhOzbwiV8mAkw AXsJ1EOKe3Z07HYvh7mwtiNYkCzinXvx2nRvTdq9EriOw0JYBJ2LYQLxEfgCUlywuDB7VXzZ8N2R EmBBipRUHNcNGVt6hJB4CcCAOLLhW5kAE7CeQB1tqF0yI/thdtiqHj4LkkLG+fnSUy7KbpWQ/wBE isKiOGsmwASUEpBLPBouXji9+3qlxSR55ixIih6A3DEbemhe7WV2+6MIMGfLBKwnsA3AxMUzc96w vujkKJEFSUE/D87fOFoT4lkA+yjInrNkAkzAVgLyv/7MhknFz/eqs7UaCVg4C5KJnTpy5I9p9W3b TBGQf2HDBRPBclZMwHkEVnr8KFg4K2e186rm3hqxIJnUd0PGlO4vPHgdAkeZlCVnwwSYgLMJVEHI yxfP6D7d2dV0T+1YkEzoq6H5ZadLIV/gKToTYHIWTMBlBIQQj6bsrLpp/vx+9S6ruuOqy4IUR5fo VnRa2Z1S4jaeoosDJN/KBNxP4DMhcW5RYc6v7m+KfS1gQYqRvR6zKCXtVQiMjDELvo0JMIEEIiCB LR4p8hcVZr+XQM2ytCmapaUlSGF5+WUDvKnpn7EYJUiHcjOYgAkEBNAlIAKLho7deI0J2SVlFjxC irLbh+SXni0EXgTQJspb+XImwASShICAeC6lsuoqXleKrsNZkKLglVdQeiuAf/F6URTQ+FImkLwE lnkatXMWzun2W/IiiK7lLEgR8KL9RQ1tsyia60URXM6XMAEmwAQMAmsDHjFq6WvZJcYBfm2ZAAtS y2z0M7njSzt7/JgD4MRWLuXTTIAJMIFQBCqkkAVLZnRfGOokH9tNgI0adrPY611u/vq+Xr9YzmK0 Fxo+wASYQOQE2gsp3h4ytnRi5Lck55UsSC30+5D8smM8wvMhB9JrARAfZgJMIBoCXiHxVN7YjXcC kmemWiDHYEKAyRu76QzIALkDyQpxmg8xASbABGImQBZ4vt+6XVFcLHwxZ5KgN7IgNevYvPyNl0EI MmDwNDvFH5kAE2AC5hCQmJ9Zh3PnzcupMSfDxMiFp+yC+nFIQdlNEOIpFqMgKPyWCTAB8wkIjKxJ xyLy+GJ+5u7NkUdIet9JkVdQRvuLaJ8RJybABJiAVQS+9kvvqcWFXTdZVaCTy0l6QZo8WWrLVpU9 JoE/ObmjuG5MgAkkJgEB8ZNPw7Di6dnrErOFkbcqqQWJvHVvF2UU2fXCyJHxlUyACTAB0wn86pf+ vOLCnj+ZnrOLMkzaNaTcXOndLja9zGLkoqeVq8oEEpfAfh7hfW/4OaUHJW4TW29ZUo6Q8vNLUreh 42tCYEzriPgKJsAEmIBlBDZDiiGLC7NXWVaigwpKOkEiMSoXnQol5GgH9QNXhQkwASagE6C4SprU 8ooKu61MNiRJNWVH03Q0MmIxSrbHnNvLBNxDgOIqSREoGjq2tL97am1OTZNmhEQGDNtE6asCosAc dJwLE2ACTEAlAVkGIXMXz+jxg8pSnJR3UoyQfreme57FyEmPHteFCTCB8ARENqS2mJw8h78ucc4m gSBJUS7KHgVwfuJ0G7eECTCBJCHQwyM8i3LHbOiRDO1NeEEaMrbsHt70mgyPMreRCSQsgQM8Hu3d kfllXRK2hb83LKEFaUjBxluExN8SvRO5fUyACSQ4AYGBjQjMG5q/vX0itzRhBSlvbOmVAuLeRO48 bhsTYALJQ0AK8UcpamfT1pVEbXVCCtLg/I2jIfEYgKSxIkzUB5TbxQSYQDABMWS76Phsogb5SzhB Gjxu0x81IV7jEBLBDzG/ZwJMIIEInJc3tuyhBGrPrqYklCCReaQIBOYByNzVQn7DBJgAE0g0AhLX Dc0vTbj18YSZ0iILlEaBjyRk0tjsJ9p3jNvDBJhAVAQkpBy/uLD7jKjucvDFCTFCokW+Bsg3WIwc /KRx1ZgAEzCbgIAQz9IyhdkZ25VfAgiSFPoin8DxdkHkcpkAE2ACNhHI1AKBuYmycdb1gjS0oOz/ AJxn08PAxTIBJsAE7CbQ1ePV3kmEPUquFqS8/LJzJXCX3U8Dl88EmAATsJnAwVLUv+R2c3CPzRBj Ln5o/qaDIeRbABJ2k1jMcPhGJsAEkpHAH3oPrNTWlkxb6tbGu9LKLu/sDfsgRfsEQB+3gud6MwEm wAQUEJBC4NyiGTmzFeStPEvXTdlRKAmkaLTxlcVI+ePBBTABJuAyAkJKvKDPILms4lRd1wlSudj0 LwDDXMiaq8wEmAATsIJAG6kFZrnRyMFVgkQ+6iTkX63oUS6DCTABJuBaAhL9Aqhznc871wjS0HM2 99aEeIEdprr2K8IVZwJMwEICQmBMXsGmGy0sMu6iXGHUkHvx2nRPddpHEDg87hZzBkyACTCBpCEg G4UUQ4oKc5a5ocmuGCF5a9IeYjFyw+PEdWQCTMBZBESKFGKGbpnsrIqFrI3jBWno2NIxHII8ZN/x QSbABJhABARkDlI8z7lh06yjBWnImNL9IfF0BMT5EibABJgAE2iRgByVl192bYunHXLCsWtItN9o O8reBztNdcijwtVgAkzA5QTqhSaPK5re/UuntsOxI6RyUXo7i5FTHxuuFxNgAi4kkBaQ4tVRo0od G8DUkYI0JL/sGAlBXrw5MQEmwASYgEkEhMRBtZnifpOyMz0bx03ZDb9gU5a/PvAFgANNby1nyASY ABNgAlIKeeqSGd0XOg2F40ZI/nr/AyxGTntMuD5MgAkkEAEhpHh2RP76Tk5rk6MEacjYjcMBcZXT IHF9mAATYAIJRqC7X3gedVqbHDNld8LoLW3T0xu/BbCf0yBxfZgAE2ACiUhASoxZUpgzxyltc8wI KT29cQqLkVMeC64HE2ACyUBACPnYiRN+6eiUtjpCkIbll50C4AqnQOF6MAEmwASSg4DITvN7pzml rbZP2ZFNfE0GvuGAe055JLgeTIAJJB0BiZGLC3Petbvdto+QajPEP1iM7H4MuHwmwASSmoDA407Y MGurIOWNX3+IRMBV8TqS+qHlxjMBJpCoBA6oyRB32N042wRp8mSpwe99AhApdkPg8pkAE2ACTEDe OHjshkPt5GCbIC0r2XQlII+zs/FcNhNgAkyACewi4NWkeEIfLOw6ZO0bWwRpZH5ZFynkPdY2lUtj AkyACTCB8ATEse9/V3Zp+GvUnbVFkBqaxKiDumZxzkyACTABJhATAYl7cs9aa8vfZ8sFadi40qMA 2KbAMXUQ38QEmAATSBICAuiipabfZUdzLRYkKQIB+R8AFpdrB1oukwkwASbgTgIC8uph+RsOs7r2 lgrDkPxNFwHiWKsbyeUxASbABJhAVAQ8AWgPRXWHCRdb5qnhd48MPwDobkK9OQsmwASYABNQTEAI eVbRjO5vKi5mV/aWjZBq0sVNLEa7uPMbJsAEmIDjCUgpHjjyCmnZXlFLBGnEmC3ZEPJmx9PnCjIB JsAEmEAwgQM7lJddHXxA5XtLBMmX0vhPAG1UNoTzZgJMgAkwAQUEBG63KkSFckEafk7pQZC4SAEm zpIJMAEmwATUE9gn1Z/yV/XFWGB+7feARkdeKxrDZTABJsAEmID5BITENXnjN+9rfs575qh0hPT7 Jthz9iySPzEBJsAEmIDLCGSJQODvquusVJACAZC/OstMy1XD4vyZABNgAslKQEp5xfD8Tb1Utl+Z IA0pKD0ZwDCVlee8mQATYAJMwDICqX7NTwFVlSVlgiQgbQ/2pIwaZ8wEmAATSEYCUpw/ZOzmPqqa rkSQ8saVngSIIaoqzfkyASbABJiALQS8Aj5la0lKBAkBOdkWVFwoE2ACTIAJqCUgxflDz9ncW0Uh pgvS0PzSE3l0pKKrOE8mwASYgCMIeOFRY3FnuiBB4FZHIONKMAEmwASYgBICEvK84eM29jQ7c1MF KW/8+kMkMNLsSnJ+TIAJMAEm4CgCqb6AuMHsGpkqSPB7yL0E7zsyu5c4PybABJiAwwgI4PIR+es7 mVkt0wQpd1zZAQDGmlk5zosJMAEmwAQcS6CNT3j/bGbtTBMkLYAb2WedmV3DeTEBJsAEnE5AXntc /voMs2ppitPT3LPWdhCQl5hVKc6HCdhNwOsVaN9WQ1amQFaGQEaGhox0gdQUAaEBaali19x0Q6PU j1OdG30S/gDQ0CDh9wPVtQFUVUlUVgdQXRNATa20u2lcPhMwk0DnLOE5H8BTZmRqiiBpqekTAcnx jszoEc7DUgIkLL3396JvrxT03T8FPXK8yNnXgy77eJTUo7pG4tdSH9Zv9OGXDT78vK4Rq39uRFV1 QEl5nCkTUE1AAtcD8mlAxP1rK24DhNxc6fV0LfsZwH6qG875M4F4CXTqoOHQAWk4dGAqBv0hFQf0 NOU3WbzV0gXqu58asXJ1A75YWY/NW/xx58kZMAGrCAjg1KKZOQviLS/ub6O3a9mZksUo3n7g+xUR SEkROKR/Kv54eJr+v3t23I+8kpr27O4F/R9+StN0/MZNPnzxTQM++7oeK76pR31D3D8+ldSbM2UC REBKXAUgbkGKe4SUl1+6FAK53C1MwCkEaK3n2CPSMfiEdBx/VLpTqhVXPZZ9WocPP6vDxyvq9fWo uDLjm5mA+QQCwu/pVzRr3zXxZB2XIOXllw2AkCXxVIDvZQJmEKCR0PFHpWHw8Rk48ZjEEKGWuJAw LXq/Fp98WY/GRh45tcSJj1tLQEDcXzQz+2/xlBqfIBVsfBQQptqhx9MYvjf5CPQ5IAUjB2fgrFOz kq/xAN4qqsE7i2vww5rGpGw/N9pRBLZ2kuXdCwsHNsRaq5gFadSo0syaDGwE0CHWwvk+JhALAbKM yzsxA6OGZ6Jfr5RYski4e0iQ5i2qwdIPa1FXz6OmhOtglzRISHleUWH3V2OtbsyCNLRg46US4plY C+b7mEC0BPbt4sGoYZkYdybvMAjHbtbb1Zi7qAYby3zhLuNzTMB8AhIfLC7MoWjhMaWYBElKeeCM udULXp1TeQDtq+DEBFQSGHRQKkYPy8SQE03bEK6yuo7J+73ldZi7sBpfr4p5BsUxbeGKuIOApkE+ PLnTjQMOSv93LDWOWpBIjAB8T4XNW1iDp1/bCRalWNDzPa0ROO7IdH1a7pjD0lq7lM+HIfDNqgZ9 xFT8UW2Yq/gUE4iPAE2fP35fZyOTvwsh/mV8iPQ1FkG6F8AuSwoWpUhR83WREsg9PkMfER0yIDXS W/i6CAjQxtt5C6t1Cz3JExsREONLIiXQe78UPDlllxgZt3mFEFHt8I5KkKSU5E9lr4lpFiWDP7/G Q+CUY2lElIXDBrIQxcMxknsfeqoCbxfVRHIpX8MEwhJoQYzontOEEPPD3tzsZLSCdCqAkAWQKP3v 5Z1s4dMMMH9snQBNyZHFHE3RcbKOwKofGnTLPNrTxIkJxEIgjBhRdjOFEFGFJIpWkF4EcEFLFSdR eviZipZO83EmsAeBA3unYPTwTJw6OHOP4/zBWgLkmohMxskbBCcmECkB8gP59INdWru8vRBiZ2sX GecjFiQpJf3VqDZubOmVRaklMnzcINCxvaabbp9zuns3s1IYCWMdJjNDoLau6XNKCnaFojDa65ZX GimRVd53P/ImW7f0mV31pC0Y40a30Wc2WqnDJUKI51u5ZtfpaASJhl7Td90Z5g2LUhg4SXxKCGD8 WW1w6bi2jqZAISHWl/qwcZMf5OR0yzY/tu8IoKIygIqdgV1CFK4RXg/QsYMHnTtq6NTRg25dPdi/ hxf75XhBZuxOTrSPqfCtamzdHtV6tJObxHUzkUAUYkSlLhJCDI+0+GgE6U0AoyPNmEUpUlLJcR15 Vrj1Guc59aA/urRP59vVDbr7nTW/+pT7hyNh7pHtxYADU3BQ31TdiKNnjvO8kD8/sxKvzK6KSICT 4ynmVkYpRgawbCHEJuNDuNeIBElK2RHA9nAZhTrHohSKSnIdoz+8Z52a6Shfcx9/UY9PvqjDFysb 9BGQE3qka2cPjjg4DUcflgayNnRSuu/RHSj6gA0fnNQndtQlRjGiql4jhHg0kjpHKkgUnvzZSDJs fg2LUnMiyfP5vDFtcMlYZ0zP0R/UpR/V4qtvGxwfW4g8lx91SBpOOCYNp+Y6w+CDnLjSd/nnX3h9 KXm+wbtb2r6thosL2kayZrT7pt3vioUQg3d/bPldpIL0NtmUt5xN+DMsSuH5JNpZp3hYIKuxhe/V 6kHu3BqmgcSJggtS4D4nxHZ66fUqvPh6JU/jJdqXNkx7sjIFJo5vF6sYGTnvK4T4zfjQ0murgiSl bA9gR0sZRHqcRSlSUu69zuMBLhvXDgWj7bOeW7fBh/lLavQpJjJASKTUrq2GYSdl4KqL2tnerH8+ VI73PmYzcds7QnEFTBIjquWVQognW6tuJII0HkDM7sSDK8CiFEwjsd4POzkDt/zZPqMFGg3NXViD L1bWJxbYEK0ho4hDB6TqXi3sXG+a/U41XplTpVsehqgmH3I5ARPFiEgsFEKMaA1JJII0E0B+axlF ep5FKVJS7riOwoVfPLYtzjnNnlHRzHnVmLeoGmWbk9NEOWdfjy5M+WfYw5+e0keeqdB/DLjjieVa RkKA9tZdPiHuabrmRXUQQoT1nNCqIAUCcqYQ5gkS1ZBFqXk/ufOznaOiFwsrMXt+DaqqE2taLtYn gf6AnD0yyzYjkjcX1GD2/GqOwRRrBzrsvusuax/vmlGoFh0phPgi1AnjWKuCNOutquvHnJ71kHGD Wa8sSmaRtD4fWiuiX0/n2vCr/IXCSsycW+14Sznre6WpRK9XYMxpmbjiPHvWmZ54aSdef6tVhy52 4eFyIyCgSIyoZE0IEdbPPHnvDpu2+f5y7uqfGo/PO8nc4Gh/6JOCju09+OTLxJ/zDwvYZSfJ0uvZ aV0w4EBrvQ2QEP3ffeX63iF/cs7ORfSkBAJAyfeN+tpOba3EkYdYG0vqqEPT0KG9B6Wb/dhZyaPX iDrNQRepEqM/3bK14sWnMqesWnVnfILUa9Ckh0s3+Tuv/qkRLEoOenJsqArtKbpuIhldWpdmzK3C P6aU47Ov6sFCFDl38rNX8kMjXn2jGo0+icMHWSdM9GPzrFOzUF0r2S9e5F1m+5WqxOiav28lLyjp dahatGbV1F/DNTTsCGnI2M19hJR3UQbk14tFKRzKxD3X94AUfVOclc5Q58yvxgOPV2Dph3U8PRfH o0UjppXfNWDGvGpoAjjYQj96Rx+ahvbtPPh5nU93PhtHM/hWxQRUidH1d2zDqh9+30wtsGltydQl 4ZoSVpB6DbxxrADOMDIgUfplgw+nHMfTdwaTRH+lX7p33dwR/XqnWNLU5Svq8PgLO/HGuzU85WMi cRpdfvltAxYva9o71L+fNVOuB/VNAVkA/rYtgJ/WsZcHE7vUtKyuubQ9Ro8w3yMIiRH5iAxKaWtL pj4d9Hmvt2GNGobkl84SAmOa30V7H26/gdzbmZvY0MFcnvHmRhswrTTnfvQ5EiJnLoiTFdthA9NA 01H7dfcip5sHbTI1tMnSdMwNjVIfBZBn8A2lPtAG3a9K6rH2170CLMfbLabcT/uYRg/PwinHWec3 j4wdyOiBk3MIkBidaY0YUaP9XunvuqCwZ4t+UVsUpPx86dkuyrYCCLnbUZUokfnof54Na6runN5M 0JrQegMFzjvpj9b8saJ1ohdmVoH+qDsppaUKDD4hA8NPzsAhA2IbUWwvD+CDT+vwzmJn+oEbNSzT 0nXBjz6v04MB0pogJ3sJqBKjG+7YhpV7jox2N1SK/MWF2a/vPrDnuxYFKS9/w3EQ2kd7Xr7nJxal PXkkwieaXrnyAmtMht9bTt4VqvXwD05iRzvUzz29DS44t42p1fr863p9Ayn9UXZS0jToa4QTzja3 veHa+NyMptAW4a7hc+oIqBKj2+7djk/D/dgQeHLxjJwrW2pZi2tIvQbedJEQGNLSjXSc1pNUrCnR vDMthn7KJuHh8Jt+jqboLjjXGu/cT76yU18r2rzFOTbcNCKiP8r33baP7prHbMA53bz6iOsPfVJR XRvAxjJntJ0s8mh9icJy0B6zvr3UrxfSKJy+4zSlSdF3OVlH4M8Xt1MSDqZVMWpqYoe1JVMfaam1 LY6QhuaXLpACEUX645FSS3jdcZz2FNHUDXleUJ30Hf3vVDsmDhG1lzaTjh1tvZcDGimR/z0aOTkp 0ZoC/YK2Kt37nx1YvIzjLVnBm8SIPHqYnSIUI71YKf09lhT23BiqDiFHSLm50ivaVP0XQEQT5zxS CoXWHcdGDc/EnZM6os/+6n8VP/x0BV6aVYXKKmdsmKTRwLgz22DaHftYuk/HeDIoSuzQkzJ0I4kd lQE4ZbT4/c+NmPVONVJThCUboGmt0uMR+KpkD4ssAxO/mkTACWJETdEgVqxZNW1lqGaFFKRex151 tJDy6lA3tHSMRaklMs49ftn4tpg4Qf16EVnO0eZWChXuhETesmmt7N93ddajtNpdpwN6pmBEbia6 dfVie7kfW7fbL9iNjdBHbr+W+nGyBRFsDx63vWsAACAASURBVOmfqntuIevE6hqewjP7mVQlRpOn lmP5iihH+EJsXVsy9a1QbQwpSH37T5oAgWGhbgh3jEUpHB3nnOvYXsOV57eDFRtdyWKSgrrV1tn/ R4aEiPZVPfqvziAXN05LfQ5IwWl5mejU0YMt2wIor7BfmNat9+mjWlpfG/SHiCZMYsZKJvW0zWB9 qR9ULidzCKgSI4qJ9cEnMRnoZK0tmfpYqNaFXEPKKyibC8hRoW6I5BivKUVCyZ5rjjksDffc2kl5 4bQ2MnNuFTY5wGiBhIj+0N9wuXXrImYApvW2eQur9T1NZuQXbx4nHJ2OO28yf/9hqHqRN/cXX68K dYqPRUHgTxeocYIcb4DGVCm6zi/M3tK8KSFHSL0GTnpYADGvfPFIqTlmZ3ymxcxbrwm5rczUCj79 WiWeea0SVQ6YeqHQ3/+7vwsorLrbElmbjh6RpW++JWG321np+tKm0VJ6qoaBikdLhw5MA0XI/ea7 BvicYYzotscHThUjAtkoRPG6kqk/Noe6lyANz9/USwp5W/MLo/3MohQtMbXXTxzfFpeOU2vSvWRZ LR5+Zifo1e405MQMkH8uFRZFVreN3PzQVGN6uoYNZfavsaxYWY9fNvpwyrFqrTIP6puqm+GvXN2I Tb+xKkXz3CkTo3+bE7peCPnz2pJpxc3btJcgHXDwDacC4tzmF8bymUUpFmrm3pOWJnD1Re2Vxy4i bwsPP70Tv2219w8HucK59rL2utFC1857Pd7mwrU4NxqV0BoLWb+tXe9DXb1963L03SaHrRlpAqr9 4tEot6IyALL+49Q6AVViNOVxMs+Pac0oRKVFw9qSqS81P7HXN7b3oJsmAji2+YWxfmZRipVc/PfR lM+F57bF6UPNd5wYXLsHn6jQg+YFH7P6PcVposXb8We1Qbcuez3WVldHaXmDDkpFwag2ujB9v6YR ZBFnRyKHrZ99XY/ynQEce4TaKdE/Hp6u7xejDbycWiYwcUJbFIw23+MGidGCYlNnPjpflP/gA8XF e8ZH2suoIa9g43JAmCZIBjo2dDBIWPOad2KG8vUicoY7Y16VrdMpZC1HfvdIkJI1vTK7Cq+9UWXr iClnXw/yz2ijIuz1Ht1Khh5Pv7rTEVabe1TMAR9IjGhfndlJgRjpVfT40X/hrJzVwfXd46ckOVSt FVX/BmD6LkkeKQVjV/ueHkrVgfTojyB5bq6qtmfaiJydXj6haR8VbTBN5kR7eMjlEfXEdz82gmIg WZ0qq6Ue/ZnqQF7RVSUa9Xfr6sGOioDt08Oq2hhLvm4TI72NQi5vvkF2D0HKOfjKgwBcHwuQSO5h UYqEUnzXkPHChflqjRfuf2yHvpM/vprGdnf/vim4dFw7XH1RO9CGUjsTTR9R8Luff/FhQ5kfASn1 zZ121YmE4PwxbdDogx5M0w5h+mZVA35c68OQE9QZPPTaLwWnDs7E9h0BikRqF27HlKtKjMizyjtL TJ2m24OZEFi7pmTaouCDe0zZ5Y0tPQ8SLwdfoOL9iNwM3HyV+ebHyR66QtUGOOMZoCm619+2xw9d v14p+nTQaUPUrocZbW3p1fDYTYEEySlp89Q2S9MNSM4bY/7USfOyWvtMDmxnvV1tS+j3lBShj2DH nBbz7pHWmqeff3lWFZ6fWRnRtYl4kSoxeuSZCt3PokpmUorFSwqzhwaXsccIqffASRcBOD74AhXv KaTx5q1+0EY7M1Oyegnvso8Hl09ohzNPVfflnzm3Gv99YaflfugO6OnVR3yTrmwPEiW7Erk9euqV nfr+KtqP01KimE7kk418wdEIhabT7EpHHpKGC85pq6+30FSelYnaTgYPZAlI9VCVaOqW9iuFDXmg qnCb8724oK0+VWt2NawQI6qzEGi/tmTqA8H133OEVFBKw6c9FCv4YrPf80gpfqL0B2/a5H3izyhM DnZEcqV1IfJArvoXdphm66dKvm/QfynG6o2a3DSRqfa4s+wfMVE/vrmgOuTIrjUO8Zy3IuAjjd4L 36pC6WZ7tx3Ewymae0mMzj/H/GfKKjEy2iok9i8qzPl112fjDb3mFWwsBUR28DHV71WJ0pz51Xjs +cQOl2yFJd3N/9ymx8pR/RwY+dOUF5luF4xWN9ozymrt9YH/7sDC98yZQ9+3iwdnjshCwSj72/XQ UxV4u6imteabep6CAF51oZrQB8EVjSYMQvB9bnqfKGKkM5cYubgw512D/64puxMn/NLRG/DcbZyw 6lXV9B1t1mvbRkOihkomx6g3XKHONxv5orvlX9vx68aWp6fMfkbox8kjd3dW7pamtXrTH2zyTk7P plmJPFiv+KYeRR/U6lN5FIPKrkRulAb8IVWfygs3/Whm/Wi9jb6LZHBxxMHqpvDyTsrAth0B/Jig xg6qxOh/L+3EnPnW/kjRny8hvlpbMnW58aztEqQDD/rrURC41Dhh5SuLUnS06aGk0BGqEnldePyF naD1EKsSmS3/5RJ1AhtJO2hK6//u367UcovMo8kwYulHTUYRqr0ctNTunH29GHx8Bsj4wMrNpt+u brJKpLJVJRJcenLJ4i+REhnKXKTAgpbEqPCtaptQiQ1rS6bONQrfJUi9Dp40EsAZxgmrX1mUIiNO bkFUrkfQw0lB9KxMNNq74jz1cZlaahO1+dZ7t+um0i1dY/ZxcpRKI4YPP6unxV0c2Nseg42DD0rV nbeSAYJViUZlbxXVIC1NA4WcUJHIBD4zQ8Pn31jXLhXtMPIkMbpkrPk/Qu0VI2qdrF9bMu0Zo527 BKn3oEkXmOkyyCggmlcWpfC0/nJJO6WL/HdOK8f8peasmYRvye6zfQ9IsSykwe5Sm96RR3ISom+/ t9YCLbgeFPPo4y/qdXGiUOoUE8nqRKO01FSBL1ZaN6Kg+FiffFkP8rWoKs4STYt2bO9ByQ+Nlo72 ze6/xBUjIiXarS2Zep/BbJcg9Rk46RoA/YwTdr2yKO1Nnhb6aUGYFsVVJLJQuu/RHVi52ro/SEY7 aPqxn8Wjg+dmkBCV61M6ofYSGXWz8pWixH74WR2+WtUACoZn9aZf8o9nbFy3st0kghSm5OjD1Kwr 0QisXRtND3hIG2ndllSJ0bPTKzFjrl3TdHv0QlrvQ/76xNpvH9Qrs0uQeg2c9A8Anfe41KYPLEq7 we/X3YsJZ6nzETb7nWrQ2okdsXbItFvFBund9PZ890JhJf7vvnJ9zcQOLwZ71ib0p81b/Hj/kzpQ yIWMdIH9e1jnFql9O49pVoWhWxf6KO2RIiexZDWqItEPnjOGZeplbCxzj1m4KjGiH2SvzrF2Wj5c v2oy8Maakmnr6RpdkJp82FVOBcQugQqXgRXnWJSAwwel4rF7OiubZ6fQ4k+/at8ud/rCDein3tqM /O7d/sB2fP51gy1eC2L5vlD8n/eW12HVj43IzBCwwl8f+Yhb9UOjLXt5SCjeXlyL9FSh7Hknwftt WwA/rbNvijbSZyH/jCxMnGD+uiqJEX0fHJWE9t6akqlfU510Aeox8KoDpMCNjqokoJvdqvDo4AaT 8NzjM3D3X9WFGievC9PftPfBJFdH7dtqyh676W9UYfK0cny8ot62EA3xNo42epJF3k/rfPo2hpxu akdM6ekCxcvNinkTXeuNdSUKRKgqIi15hSfvEbSu5NREYnTlBUkiRmTWAJSsLZm6lPpDf7r98PcF 9nDa4Ji+MmJwmD21Y0QSdeLmWfJQoNJb9z8p6qNNf3SMB8vrgbJf/TPnVYOmIrdud8/0jMGlpdeP Pq8D/acfKqOHZYJc5qhIJx6Trlv92bm29uTLO1G+w6/kjzIxu+L8droFnhN94CWbGFF/aEAf41nW BUlqYn9h3ZYTo+yIX5NJlFQ9kAbsa2/fhlU/WG+8YJRvvHbsYP7sMDkSJQ8dm7YkjhAZvIzX4o9q Qf8piuqo4Vkg7+dmp306emwXc9oXQ/14x40dzW6enh+53UlPE3oIFSUFxJCpqu8+OaB13DRdEB8p sb/xUf+r0HvQjWcC4hTjoBNfk2FNidZUVMwbU3+SJR25VdlQZp73gXieE/pjkD/KHF9cNBp64L8V IH9zZLGVDIlCXsxfUqOviZgdnHDRB7Uod4BFGnkJ+fDzemiK9mmRWTgZcnz6pf17lc4ckYmrLzZ/ Y7grvKELYG3J1Ifoe6uPkITUekp9b7Ozv8qJPFJS5RKEetSJfv0aTJjCp82VJLQ//2JCZs5+9Fus 3btLa7CguAan5WXihsvN/4PWYsEWnfh5XaPuZd7nB+iPttmJ8iTBe+H1Sj3on9n5R5IfRTy+5lLz +84VYtQEKCc3V3qLi4VPHyEdMOCma4RA70jg2X1NIo6UKKYJuc5RkWio/uQr9lnStdQm2ogZT7hl cm1EFkO0sZQTdN9t5GFjZ6XEMYfHt6eHhN4JIySjX/1+6KMYVcYOtFcpI03Tpwgrdlr7PJEYXXtZ UosRdbOW0rby6Z+/nbZTN3ESQvY0Ot8NrzRSomiGZicydCDLLyuT7groTDVi9NQrlfofbSvbw2XZ R4AMEd54txpTHt9hXyUUlkzGDuRdQ0UaNTwTz0ztAnKlZFVSJUZkPetEg41wXGUA+9F5w+a2R7iL nXhu3qIaUOwOs5OVokTid+4Zarwv/PupCpCTVE5MIJEIvPZGFR58wvzvvcHooTv3gdlrckbewa8q xcjOvYXBbYzmfUBqetgjbdSoUpqYNX9yNpraxHgthUhwqyjRnLFheh5j81u87Z8PlevOK1u8gE8w ARcToHUzitOlKt11c0cMO1mN1wiqM4VZUTFNRyMjN4oRMZEIdKNXrTrTY2lAPrMfIpWiRNNpZidy B3PdZe2VLNBSXW+6axve+9iejY1ms+L8mEBLBChkxgXX/qYbtbR0TTzHb/lzB9AoxuykKiCpm8WI GAsNXejVK4Xs7OQ9SJE8ECRKlMz+1WFMpz3xkjmRZ7MyBSaObwearzY7kbXZ7Hersd7CgHpmt4Hz YwLRECjb7Mdjz1cgINVY4NHfE/JcMdMkJ6QsRmF6V4ocOuvVAgFXj5CMJjpdlLp29mD8mWqcpJIY Pf3aTlBUUk5MIF4CNbXWWprFU18yB//PsxXw+SQorpbZieJ0kfd18vsYT1IlRq+/Ve3aabpmPHXH 3l4BdEmUP2NOFaVuXTwYO1qNGJFVFXnr5sQEzCIg3aNHu5r8+Is7dR91tLnc7ERRWj2aiNlyjdw9 me36jNpIYmTW7I3ZzKLPT+xL93ghRQe4fc4uqPVOE6Xe+6XgySlqonok1gMZ1In8lgnEQID2pdXU Slx+nvmRVcnVkNeLqEcjpxybjr9f1yGG1oS/JfG++1KHpEnR9CZ88911lkRJhfUdrSlFY+jQr5c6 MaJFzMT5deSu54tr61wCtNVh2pNqzMJpI3c0338So9tvMN8XX+KJkf486aA0IPEEiZpntyhRBM7H 71MzMiKXIG4173TunzKuWaIQeGdxDf4xpVxJcyL9UapKjMgNWIL+EG0aIQkI8yVcyaMQfaZ2iRIF 1vv3nftEX+EI7qBpCbftwo6gWXwJEzCVAIXquO4favYqkSiF8+hysqKRkRN9UprYaem5F69NJ08N 5jtSMrGW8WZltSgdc1gaptyuRoz+99JOR7uRj7ev+H4mYCaBku8bcOmNW5TsVaJN7aEcotL3/x8K pukSXIyaur06q4NXAubv/jTzqTIhLxIlSqr3KdHDeM+taqK8PvxMhZIvlgl4OQsm4FgCFMLi+cIm /3dm7/8zPIXTd5OSqu9/UogRgFQEMryQSHdosFhTH3LVovTFynplYnT/Yzuw6P1aU3lwZkwgWQiQ B+9HnlWzgdYQueUr6pR8/5NFjOhZDABtvBCIz1e9i55qlaJkeHUwG8fkqeVY9im7AjKbK+eXXATI EzptoA0EpOk+JEmUDGEyk+qbC2rw2PPJs8fQ70EmBehL6DWk5g+IKlFqXo4ZnynC66df2R/N0oy2 cB5MwAkE6A88xVdS9QPSrDaSGJGAJlMSgUAaCVLSjJCMznWDKN04eRu++a7BqDK/MgEmYBIBMpuu b5BQ4dXBjComoxgRN02KtmRll24GRLflQaLk1OHw9XewGLnteeL6uosAbZ+g/XxOS8kqRkY/0Agp aRMtGHo9wJUKwkzECvWKm7diza+Nsd7O9zEBJhAhAdrP1+iTuGSs+a6GIqzCHpeRk+Rkm6YLBiAR aJPUgkQwCt+q1pnYLUr0ML65sBrr1vuC+4jfMwEmoJDAK7Or0NAgbf9RSt9/w3xcYXMdnbUAPCRI zvh5YCMqu0WJHsbpc6uweYvfRgpcNBNITgL0/acwFuG8L6gkw2K0my4JEq0jJX2yS5ToYXx5dhW2 lbMYJf1DyABsI0DT936/NH3zfGsNYjHak1DST9kF47BalOhh5MB6wT3A75mAfQTI0Ims71TELgrV Khajvanw6KgZExIl8hmnOrEYqSbM+TOB6AksKK7FPx9S4yk8uDb0/f/fy+r/zgSX6Yb3JEgujA+p Fq1qUWIxUtt/nDsTiIfAex/XKRUl4/tfV58osbrjob3nvSRITZ4H9zye1J+yMgVy9lU3m0luRkYO yUxqxtx4JuBkAocMUOcvgL7/+WeYH2rdyTwjrZu6v7qR1sBh15EYTRzfTolvquCmUuRJEWR2Hnwu Gd5npFPrOTEB5xG47rL2yr//FBKdEsc2293/EvCzIO3mAavEyCiS9j4JAcyc17QXyjieDK8eXr1M hm52VRszMwQun6D+x6gBhUXJINH0KqBV0Z8FdiUNWC5GRldccX47x/rUMurIr0wg0Ql0bK9ZKkYG TxKliwuSfiuogQM0Qkp6d9JWj4x20f/9DbkuSfEKHr43BxPmcy0vCIehw6eiIdCtiwdjR7dRPk3X Up14pNREJiBkJQlScvk4b/ZU0DDdijWjZsXu9ZEeSq8XePpVtjHZC06IA7SJkRMTiJdA7/1S8OSU zvFmE/f99P0nv3rkyihZk9S0eg0yuUdIVs4Zt/agjTuzDSZO4OF7a5z4PBMwg0D/fs4QI6MtNFPi 1JAYRh1Vvnr8qNEgkncNyQprmmg7kESJLPA4MQG7CGRkJL4F5CEDUvGfu+0fGTXv42QWJQ2o0gSQ lNuFnShGxsNJ0SztcvRo1IFf3UugPs64jhr9VUjgdPxR6Zh2xz6ObWGyilIDtNqkXENyshgZ35Kz R2aB/jAkc3wUgwW/RkegsZHX11oiNuzkDNzy5w4tnXbMcRIlSkm1ppRVvUOTkOodNzmmmwFVYvT6 73GVzGzqmSMy9fqmpyX2L1YzmXFeTKAlAqOHZyoTI4pAa3YiUco/I8vsbJ2aX13x873qvIDY4dQa ml0vVWJ0273b8elXTdbzNN1mZiI3I5QK36pC6WYOUWEmW84reQgUjMoC7flTkcgZK/m/o2SMbMwq xwgcakQiMCtfB+aj65AmZHIIkhVi9MRLO6FipESi9OIjXXFQ3xQHPkdcJSbgbAIXntvGEjGi6bWX Z5lvtk2ilAQjJX2mToOQCT9CskKMjK8kiZIqV0CP/qszjj5MndNHow38ygQShQBto7gwX81Witsf 2D0yMniRbzoWJYNGNK9NAyNNAluiuc1t11opRgabJ1/eielvmv9LifK/99ZOGHJChlEUvzIBJtAC Ado+QdsoVKS/3bMdy1eE9rqmUpTMXhJQwSa2POVmuk8LaFpZbBk4/y47xMigQh4XVPxSovxvu7YD Rg3j8BUGa35lAs0JXHNpe6j64339Hdvw+dfhPa6pEiUSWTLOSMC0ldrkFVLobxKtgXaKkcGSHkpy B2L2Qiflf93E9qANjDPnJp+ncIMvvzKB5gQ6ddBwwTltlfmlu/KWrfh5XWPzYkN+pu8/JcNXXciL Yjh47WXt9bso5HrCJCFLqS3erBp/WU2CzQA5QYyMB4UWOn0+4PLzzJ/HvuK8dsjK0KDC5NSoP78y AbcQ+EOfFDx2jxrvCxTldfa71Vi/0RcVDhIl8lFp9tRhoomSDDQtHWnz5uWQzCaM1DpJjIwnd8bc KvzvJTUOMcj31VUXqjFnNerPr0zA6QTI+4JKMZo+typqMTKY0fS9ijVlEqVEmb4T0DYRLyNM2gYD nptfad7Y2LdjZjuC9xnFmi/tI3jkGTWO1c85PQvU9pQU3kAba//wfe4lcFpeJu66uaOSBtDI6OnX dmLzlvj2AKoUpTOGun9NSRMB3ZZBjxgrpVgvhDxQSY9alCn9QSbPBmanux4q37XpNd68ac63vkHi 5qvMd11CbScXZLPnV2N9aXTTCvG2i+9nAnYRoDhGKqbDqT1vLqjBo89VQJrkickILWP29N31l7fX 16oXFNfa1Q1xlys0/EqZ6CMkoQXWx52jjRmoEiPagf3+7zuwzWoePTQkcioSjQ6fe6gLDh/Ee5VU 8OU8nUWAjIVUidGc+dW6H0mzxMggp2qkRD9yR+S61hjA37ApRx8hNU3ZSbhWkFSKkeEOxHiYzHol kbv5n9vNym6vfKbc3glDT3Ltw7lXe/gAE2hO4OqL2imLHUTeVh57Xs2aL7WDREmFRxcXi1JpcbHQ p3V0QZJC6MOl5p3u9M9uFCOD6Zff1uNPt2zFO4vV2JP87S8dcM5p5vrVM+ruhFdaxD7qEB4JOqEv rKxD504e3eHwGEXP9qtzqkDeVlQnVW7GXClKQQMifQ0JkL8C7loQd7MYGQ/7T+sa8eLrVfD7ocQY 46qL2qFDew3PvGa+J2KjDXa9nnB0Ouh/8Ue1mLuoBt+sijMIkF0NMbFcjwf6jxBVTkRNrGpMWR06 IBVTFcYxou0TVoZ7MITP7A28JEqNPmDJMpesKQUNiHRB8gY8P/pFIKaHxI6bEkGMDG5bt/vx3xeb fpGpsBAcf1YbZKQLPPqc+l99RpusfM09PgP0f+F7tZi3sBrf/RTZpkUr66i6LCEAGjEksvk/rY/Q H1pVibZl2OFRW5Uo3XZNB/h9cpcXclXczMlXrjHy8dCbIwZOqawVlbcCQv9snHTiayKJkcE3EAA+ +bIebdto6N8v1Ths2utBfVPRsb0Hm7b4UbHTGT88AhIgsTQr9TkgBWT+26mjB1u2BVBe4Yx2mtW+ UPmQEJ11ahZ0p7uHmjd9+eaCauxwyHNC7R53Vhtcc0mTd4JQHOI99u+nKvDGu2qmziOp2+ff1CMz Q8OAA8397p9yXAZ+2eDT/0dSD7uuEZDPrymZ9iWVrwvQqlV3yt4DbzofgHPj+gL6XhsVpt3B8Uzs 6hQq97Ov1DyYlDftYj9zRBbW/OrDr1HuNlfBhKYUzj2jjel7pw7snaL7+WvXVmsS4MrEFKbTh2bi v/d2xjGHmydERj/TNHJdvUm2zkamMb6SN5ILzjXvh0vzakyeWo6iD+yf2iJRUvGD1BWipImpa7+d qtsx7BoR9Rk4aSSAfs07zCmfE3FkFIotPZgej8Ah/c39tWSURdNbldUSqx0wtdW/Xwp65vy+jGlU 0KRXGhWSAGdladj0mx87qxJDmIafkoHrr1DnXLeqOoDnZ6rxVB9N12ZmCPzpwvagTd+q0rW3b8OK b8I7SVVVdqh86QdpMoqSP+C9bd2qKfpDt0uQeg2adAyAY0OBsvtYsoiRwfmrkgb9F+qRiqzIjjks TRc9KsfOJCFw8rHpSqswoF+qPq2Vnq5hQ5kP1TXO+OUfbaPph8S1l7bX14q6dNr1tY02m1avX/5F vel771ottNkFBx+UiosK2mLkYPM3ulNR5H2BRkY0neW0pFKUflzr078DDmvzzqWF3f5u1GnXk917 0KT9AZxhnHDKa7KJkcG95IdG/LY1gOOPVvMHm0ZgNK21YmW9aTvRjbpH+kpThxeca77T2VDlD/xD qm6BlpoisHa9zzFTUqHqGnzsxGPS8eeLm+L67Ntl19c1+BJT39Pifumm+NzkxFMhcoMzeVJH9NpP TXRk8r7wyLMVqKl17g8TVaJEcdRoZmSjjf0b4tn4Zm3J1KeM47ue8D4DJmVA4FLjhBNek1WMDPZk Fk4PUJ6iTa40rUW/trduD2BbufVTWrQLnkYsVkbBHXRQKgpGNa1d/bCmEY0ONcr74xFpuPrC9nro gpx91UxrGs+Z8frdj436pk3js9Wv5HlBpck6bUb97wvusDZVJUr0t8RhorR4bcnUN4xnbZcg9Rt4 fVVAaLcYJ+x+/csl7fSpFrPr4RQDhkjbRb9mPvi0DgJCN0yI9L5Ir+vbKwW0QL55qx8/r7N+CoP+ CLZv58FBfdX8Im6JA00LkZUfrddRHWgvmBPSUYem4U8XtsPFBW3RI9saITLa/ez0Sqz5xfpngJwC k+cF+qGgKlGwzKddth+PREnFd8NJoiQhX1tbMu1Do993CdLPqx6q7T3wxisBYc0cilGDEK80RXH2 SPMXM+9/bAeWfBg67HCIajjm0I6KAFaubkCbTE2JKFFDaZMppa9t2GBK0Tc7tPMoa1u4jqSpSwrh QRM4q35oBJng25EOGZCKyye0xcQJ7ZQZeoRr18x51SicZ32wx4EHpuLisW11k/1w9Yvn3JMv78Qr c+w31IilDZ9+mdiipEnt32tWTf3RYLNLkOhArwE3jxQCvY2TdryqEqMpj+/QN0/a0SYzyiQzaZV7 laiOhw5M09eVSJSsHDHQ1N2n9GuwrfUjJaNvDhuYpk+PEWea0rBKmPr3TcGl49rpI4QDelo7SjTa To5En/h9c7ZxzIrXkUOawkb03l9du+/9zw68VWTfHiMzOKoUpa9WNcQdWiOeNgrg1jWrpu6Ky7OH IPUeNOkQAMfHU0A896oUIze7Zg9mSsN4r1fgYEVm4bSuRCOGb75r0PfxBJet+j198eoaJFRZF0ZS /yMObhKm2jqJ739uVGbw0Wf/FN2SjEIH0KZeuxK5ynnyFetdS106ri2uPF9tYMnrbt+m/4izi62Z 5aoSpRG5mfji2wb8ttWWOettiwtzCjVkfQAAIABJREFU/i+Y056CNHBSVwBjgi+w6j2LUeSkv/y2 AVWKjQGGn5Jpy36lku8b8frb1boQqBLdSEjTWs4F57RFTZ3U15giuSeSa2jf1QXntMHNV3cAbeK1 Ky1fUYfHX9iJtxU5922pXZ06aLj8vHYw239bcHlk1v23e7Y70cQ5uJpRv1clSqcOtkeUpBTL166a +mIwiD0Eqc+gSWRz9OfgC6x4z2IUPWVaiCevC7Q/RVWi/UoZGZrlmwdp2oxEd96iGgT8AFnG2ZVI mC48ty12VjWNmGKtR86+Hpx/Tlvcek0HJe6hIq0X+fp7bnqlbk1ntfkvmbBTmHHyGqIqGaEjGhqc a9YdT9tJlMgNmNkM7RAlTcjZa0qmLQrmsYcgHTngwfJaUXUTAHVPTHDppH6KDBhozShRpumaIdv1 kfbxqLTAo4Jo0blrZ3tMw8l9DU0nLHivVl/Tor1EdiVy0UPCtG1HAD+uidxWvMs+TUL09+s7mu6r LFoW0/5XgUeeqcDPNljSnX9OG9D0pMr0YmElnplu/fSjyjaFypvWkhNBlITEY2tWTVsZ3Ma9Yk7k FWxcDghLPDawGAV3RezvyckmsSRHmyoT/TGjMOx2pe7ZXowelqnUnUwkbaNRBnkWX/R+bYtrTB3b a/pGXHIManci56E0NWd29NNI2kWbry/KbwsVPiiDy3/46Qp9RB18LNHfX3dZeyVha668ZSt+Xhf5 j65YOQuBAUUzcr4Lvn9vQRpb+m9IXBd8kYr3LEbmU504vq3uGdn8nHfnSFMihsv83Uetfdeze5Mw qdgaEG1LSJRopEpGEB4N6NhBQ98DUkBTfXYnCjlCnrvtECJqO7mF+scNHZVj+L/7tieM8UK0sFSJ 0hU3b8WaX5WKUsVJA7I7TZ68Z9yjEIK0cRykeC1aMNFcz2IUDa3orqXF4j9doNZ6iUK70wjBbl94 ZKlGMaTI3Qyn3QTI/Y9hGLL7qLXvaGMvTdOpTGS88PLsKmwrt8VCTGXTosrbpaK0aPHMnOHNG7rH GhKd7HPQLTXQpLIREotR8y4w9zNt7ly3wQdyO68qHdDDC7LCa2gEvv3ePgetFPPo4y/q9bAdZApv p/m0KtbR5EuRgW+9dzu+/V7pL9uwVdqvu1f3MpE/Su30MW3kJTdANDJN9kTfQRWb5unH3rJP69XE FhPylbUl04qb991eIyS6YEhB6W8C6NL84ng/sxjFSzDy+7t38+Lc07OUzDEH14KcVU5/swpbttn/ K5W8HdAak0rLw+C2O+X9C4WVeO2Navh89v5xpj9g9GtddbIruqvqdsWTf1amwMTx7ZR835VM3wlt 1OIZ3d5q3ua9Rkh0Qe+BN51EMd2aXxzPZxajeOhFf29lVUCfV8/KND8SZXBtyAcdCd9v2wIgZ7B2 ps1b/Hj/kzqQp3QK206/1hM5kX82GhF9sbLBMs8SoXimpQndKSo5R1Wd/jGlPOGtZ2NhSE6CVY6U Fr5Xq+99jKVuoe5Jlbj+p1VT97KQakGQbuwJiGGhMorlGDlOHHOa+UP4ZDDtjoV38D3kJ67BB5AH ApXp+KPS9TDMX5XUg8KT25nKNvtRvLwOZA1Hgd5UBQG0q43kXeH2B7brU5VWungK1V4ajf7v/s6g uFMqE60X3fVQue7WSWU5bs5bpSileIW+FGBSPLHvFhbmTAnFOqQg9Rn0Vz8gJ4a6IdpjtMCuIuoj mbLOX2J/6OFoedhx/berG/S9J4MVbqKldg04MFXf/PnLRp8jgp9RXJ+lH9Xhp3U+PRJnTjd3j5im v1GFO6eVY/mKekeEzZg4oa3ug0/1M134VjUee36na4MrquYTnD+JEq0jZ2WY64iZNuKmmidKs9eW TN1ruo7aEVKQ9usyZZOWVTUJQFw/e0iMVLgIof0wtIufU+QE1pf68M6SWqSlqgljEVyTU47NAE3j kLcFJyRqe9EHtfi11I8O7TRYEejOzHbTAv49/9mBDz6pc0RgwWOPSMNVF7XHqbnqrRtpi8FLr7vT U7eZz0A0eTU0Sn00QwJipkcHs0RJSPlQ8w2xRvtCCtK6dXcG+gyYdAoE+hgXRvuqUozs3JwZLQcn XU9RMmmXN8WfoXhAKtOgP6Tqng0oTLRTQkWvW+/TvT5s2uJHp44ePTihSgbx5j3r7Wrc/98KFH9U 65jRAe11u3Zie0tiNd3yr+1Y/AHPgsTyHNHUGlnbOlGU/H55/brvpoWMlBhSkAjAAQMn7S8EhsQC g8UoFmrW3UMjF4oSe9xRasKjB7eEzM8zMzR8/Z29C+/BdSLXOfOX1FjGILjsSN6/8W41pjxeoY/q yDjFCYnWCGktmLxDq07U/kl3bQeNbDnFTkC1KJERRdQRlwXWLH29+90ttapFQeo98AYfhLispRtb Os5i1BIZZx3/cW2jbpGmKhJtcGv1taUxbWyLShtcl+D3xOClWbSxMoDjjlQvzsFlh3pPcXumPlGh W5FV7HSGENH+LgoaSBGcaSuB6vTcjErQfiq7jTVUt9Oq/FWKEu19ilqUBApbWj8iJi0K0lEDp5bV iqprAUT8TWUxsuoxM6ecHTubTMPT0zRY4biUotKSb7MNZT49tIU5rYg/F3KWSsK0s1KCnKhancik lox0SJBos69T0ojcDDx+b2dLng1qM5l0v2NxOAynsFZZDxKlX0t9GGNyFG5aU4pWlATEA2tKppa0 1N6QG2ONi/MKSmcDONv4HO6VxSgcHeefOy0vEzdeoX5To0Hi6dcqQVZjTkvkqJb2VV2p2P0StZvW huYuqsE3NoSND8edfAWeOTxTubNeow40RUcjI/rDyUkdgd77peDJKZ1NL4BM8p9+LSIryIBX+rss KOy5vaVKtDhCohv6DLipEwROb+lm4ziLkUHCva80fbXkwzp4NHMtc1oiQvuiaCqPDC2ctlZAZrOv zKlCQ4Oa/VsffV6nmzFPf7Pa1vDRofrmgnPb4I4bO4IiB1uR6IfJs9Mro1+LsKJyCVYGjb7JFRB5 1DAzGSOllasbQLHMwqTPFxX2eCTMeYQdIeXmr+/rEZ4fw2XAYhSOjjvP0Y57CmNuVZozv1p3Bkqe FpyWPB6A9m+dPjQzbstEmpKjX5M//2KvR4tQjIeckKH/oVJtfWmU7RQHvUZ9kulV5Ujp4WcqwqG8 Z/HMPUOWN784rCDRxXkFpd8DOLD5jfSZxSgUlcQ4RtE9J09SHzogmBYFWCPvzQHnLKMEVw8d2ms4 8uA0HH5wKg7okYIe2R60ydL2uMb4QKbltFb2/U+NesTdVT80wOc8vdVDZYwengmasrUq0b6qJ18O afVrVRWSvhw7RElInFRUmLMsHPxIBOlBALRJdo/EYrQHjoT8QPuVaDf+OQrcPoUDZncgwHB1a36O XBNlpGtITxfw+yRq6qTugbqx0dnrISSk487MwrgzrRsJE7up/yMPK7ypvflzZMfnfr1S8Ph9ataU QoyUtneS2V0LC0XYn2Vh15AIUu+BN9H8wkXBwFiMgmkk7nsaqZAvvPKdARx7RMTGlnED+eMR6Ti4 f6rulYCC3zk50Zw5hUCg/UJVNRL1DdKxIzziqGnAhLPb4P7/64RBijdHB/cbGS7c8eAOfPOdM7x3 BNctWd9v3xHAF9824NTB5o6OaU2JQqzTJvygNOutwnazgj6HfNuqIHU64cGN6XW7zb9ZjEJyTOiD P/zciIXv18LjESDv3lak7K5e5B6Xgb69UnQT8dLNYX9YWVElV5dB1oMU4v4/d3fG4YOsNW0n9z/P z6zi2EUOfIJ+2+q3RJSkEHevLZm6qjUErU7ZUQZDC0pnSiCfpm9UDPEp0Nbsd6pbqyufdwABKyLS hmqmU02kQ9XVScdIiGh96IbLrTPpN9pPBhxvLqwGuWzi5GwCNFr+9537mF5Jipf2n2d3NAqZ0aWo sFNYiwcqPCJByhu7cdzE8e1eUyFGHGzL9GdAeYb79/DirBHqg/+FasiyT+t0x7orvtljOiDUpUl9 jIRoxCmZuOkq64WIwD/1SiVmzHXePrOkfihaabwqUVr6Ue36ISdk7tdK8frpiASp9LfGU7O7eOdH kmE017AYRUPLeddSWJGrLmxnS8U+/6ZeN6H+8LM6W8p3aqFkpj5qWJbu6seOOvKoyA7q5pV5+KBU TLnd/JESgH5CiJ9aq2lEgiSlfA3AuNYyi+Y8i1E0tJx7bbcuHj3ECK1P2JHW/NqoC9O7xbVwumWb Sj5tszR9H9Gl49RHbW2pHbRW9PpbPPXeEh+3HD/msDTcc2sns6vbTQixubVMIxWkCwG80FpmkZ5n MYqUlHuus9r1UCgy09+swluLakB7gJIl0X4S2nk/api5llLR8CMLOgqi58SNzdG0g6/dTcBkUSoW QgzenXvL7yIVpA4AylvOJvIzLEaRs3LblakpAhcXtEXBaHtGSwavT76ox4L3an6PrOrs/UBGnaN5 peCHucel47QhmZY5Pm2pfryvqCUy7j9uoihdI4R4NBIiEQkSZSSlfBPA6EgybekaFqOWyCTW8aMO SdN/tZN3b7sTWfkUfVCD1T81QrpYm8hI4ZD+qcg7McNSrwot9R+NRskHnVO9arRUbz4eHQGTRClb CLEpkpKjEaSxAKZHkmmoa1iMQlFJ7GMFo7Jwxfn2GD2EIkvrGxQGfNWPDa4QJxKhAf1SccIx6SCW TkhLljV5KP92NW9wdUJ/WFEH+mF5500xuxFbJIQYHmk9oxEkmqSOacWSxSjS7ki869q20XDe2W10 wwcnta7og1p8+mU9Vqysh1OC4RGfrEyBwwam4Y9HpOlTck5i9uhzO0HrRZySj8Apx6bj9htiEqVL hBDPR0osYkGiDKWULwK4INLM6ToWo2hoJe61hw5IxejhWTjlOPun8ZpTXrfBh5XfNej/v1/TiNJN PstGUB3bayBXKxQgkTwoWOUJozmDcJ9fnVOFFwo5ims4RslwLkZRai+EiNiTbrSCdCqAiPcjsRgl w2MaXRuHnZyBW/5MNjLOTuRzjTwMbNzkQ+kmP0o3+0ARdmMZTZGT2k4dNHTu6EGPHA96ZnuxXw+v 7mm7a+dWvXfZBmreoqZwGWRaz4kJEIEoRalQCFEQDbloBYm+PRH5AWExiqYbkutaWhspGNUGl59n 356ZWIn7/UBFZUDf80SOVBt9TZYSXo8Atcv4nJmu6Y5MMzMFaI+QmxJZKc5dVA165cQEmhOIQpRO F0K80/z+cJ+jEiTKSEp5L4C/hcuUxSgcHT5nEKCRw4Sz2oCilHJyBoEHn6jAu0s5PIQzesO5tYhw psMrRPhwE81bGIsg/QHA6uYZGZ/Zh5VBgl8jJdC+rYb8M7Iw7iwWpkiZmX0dGSy8uaDasrUzs+vP +VlPYERuBm6+qsXp9/uEELdGW6uoBYkKkFJ+AODE5oU9N6MSr8xmh4rNufDnyAjQesroYZksTJHh MuUqms14/W0WIlNgJmEmYUTpICEERRuPKsUqSJcCeCa4JBajYBr8Ph4CZHlGUWp5xBQPxfD3khDN nl8NWhPjxATiIRBClJYJIU6KJc9YBYl26e0aCrEYxYKe72mNAIXZPuvUTN0dUWvX8vnICDz8dAXe XlzDHhYiw8VXRUigmSidJ4R4NcJb97gsJkGiHKSU1wJ4+LHnd2LOfN4stwdV/mAqAa9X4PS8DFxz qT2xfUxtjA2Zfb2qAXMXVuP9j+t4jcgG/slSZJssbdurj+57aVaWmBtrm2MWJCrw7oe3n7j0wzpa T+LEBJQTILPqIw9Jw+jhmTj+KOdtsFUOIMoCFhTXYt6iat2PX5S38uVMIGoCAuL+opnZYS2wW8s0 LkGizPPyS5dCILe1gvg8EzCTABlAnDE0ExPOZsu85lzJ6ek7S2qwoyLQ/BR/ZgKqCASE39OvaNa+ a+IpwARBKjsXQhbGUwm+lwnESsDrAY45PB3DT8nAicck76jpvY/r9FhQX5XU87RcrA8T3xczAQEx t2hm9pkxZ/D7jXELUm6u9Hq6lv0MIKKY6fFWmO9nAi0RoP1Mg0/I0GMFDTootaXLEub4N6sasPjD Wn1tqLKKR0MJ07EubIgATi2ambMg3qrHLUhUgSEFZTcJyCnxVobvZwJmEdinowcn/TEdJx+brscR Mitfu/MhH3sffV6H95bXYcs2ttm2uz+4fJ3AqsUzswcBIu6IY6YIUu5Zazt4UtPWA+AJfX5CHUeA zMePPCQVfzw8HUcflgba5+SmRNNxH6+ow8df1INHQm7queSoqwCuKJqZ85QZrTVFkKgiQwrKHhGQ 15hRKc6DCagk0LO7F4f2T8XB/VP10VOXfZzjcZsct67+sRG0FvTFtw1Y84u7I92q7EfO2xEEttZI /37LC3vWmlEb0wRpeP6mXn4R+AGA14yKcR5MwCoC7dtpOLB3CvockIJ+vVLQM8eD7vt6kZZm2tcj ZFPKNvvx60Yfftnow/c/NYBiMW36jafhQsLig84kIMUdiwuz7zKrcqZ+4/IKSl8GcJ5ZleN8mICd BMhIonMnD9q1FaBpP4p+2yZT6EKV4hWgDbspv//8CgRoszjg+X2wVVsndW8IdQ0S9fUSOysD+nTb zqqAbo69dbsfPtYeO7uXy46fQJVX+vdfUNhze/xZNeVg7mjG438Afs8EAKYKnVmN5XyYQDQEaPqM /nNiAkxgbwISeMpMMaISTF3dXfxaz29EFBFl924iH2ECTIAJMAEXEGjwavIhs+tpqiBR5aSG+8yu JOfHBJgAE2ACziEgIF5ZOL07WVabmkwXpMXTcz4A5BJTa8mZMQEmwASYgFMI+ODX7lZRGdMFiSop Ie5UUVnOkwkwASbABGwmIOTL8fqsa6kFSgRpycyc9wEsbalQPs4EmAATYAKuJODzBwL/UlVzJYJE lZXAZFWV5nyZABNgAkzAFgKvFBf2/ElVycoE6fdR0iJVFed8mQATYAJMwFICDR6pKV2OUSZIhEnT cFvTYMlSaFwYE2ACTIAJmE5APrWwsNta07MNylCpIC2anvM5gFlB5fFbJsAEmAATcB+BGq8vVdna kYFDqSBRIR4/bgfgMwrkVybABJgAE3AXASnwyILZXcpU11q5IC2clbMaAi+obgjnzwSYABNgAkoI bGvwND6gJOdmmSoXJCrP25hCo6TqZmXzRybABJgAE3A4ASlw97JX9y+3opqWCBIN9SQER5S1oke5 DCbABJiAWQQEfqxon/2YWdm1lo8lgkSVyKqlEOeitLUK8XkmwASYABNwBgEZwC0rnhSNVtXGMkGa Ny+nRorA361qGJfDBJgAE2ACsROQwHtLCnPmxJ5D9HdaJkhUtZP757wACTIF58QEmAATYALOJeD3 yMD1VlfP8kB6eWPLjoaUH5sdi8lqcFweE2ACTCBRCUghH18yo/vVVrfP0hESNW7xjOzPADxrdUO5 PCbABJgAE2idgAS2BOobyMuO5clyQaIWpkpBjd1heWu5QCbABJgAEwhPQOC24jd62fL32RZBml+Y vQVC/F94KnyWCTABJsAErCQgpPzk5P7Zts1g2SJIBPik/t2eAPS1JCt5c1lMgAkwASYQmoDPr8kr J08WgdCn1R+13KghuEmDx244VJMaWd15g4/zeybABJgAE7CYgJBTFs/o/leLS92jONtGSFSLpTN6 fA2IaXvUiD8wASbABJiA1QTWeVI9SmMdRdIgWwWJKphZKwmC0hgbkYDga5gAE2ACyUpACnH1wpe6 2e5v1HZBIg8Omha4nAP5JetXgdvNBJiAzQReXjIje77NddCLt12QqBaLpvdYDOBJJwDhOjABJsAE kojAJq/0X+eU9jpCkAhGXV3KzQB+dQoYrgcTYAJMINEJSImrFxT23O6UdjpGkD6c26VSCslTd055 MrgeTIAJJDgBMd1q56mtAXWMIFFFl8zovhCQj7dWaT7PBJgAE2AC8RAQpV7p+3M8Oai411GCRA30 pHnIDv4HFY3lPJkAE2ACTABSQF7qpKk6o08cJ0hkehjQtAsB+IxK8isTYAJMgAmYRUD+t2hmzgKz cjMzH8cJEjVu6fRun0DIe8xsKOfFBJgAE0h2AlJgdWatsNUbQ7g+cKQgUYX9m3P+CYmPwlWezzEB JsAEmEDEBBoAnEd7PyO+w+ILHStIxcXCJ/2YIIByi5lwcUyACTCBxCMg8dclM3K+cHLDHCtIBG3J 7JxfpBRXOBkg140JMAEm4AICby8uzH7E6fV0tCARvMWF2a8L4Amng+T6MQEmwAScSUCU+j24GBDS mfXbXSvHCxJVtVr6b4TEl7urze+YABNgAkwgAgI+aHJc8Ws5WyO41vZLXCFIywt71krNkw+gwnZi XAEmwASYgEsISIhbF0/P+cAl1YUrBIlgLpmx789CyIvYK7hbHi2uJxNgAjYTmLNkZrepNtchquJd I0jUqqIZ3d+EkA9G1UK+mAkwASaQbAQEfhQy/RI3rBsFd42rBIkq3imQc6uUgsJVcGICTIAJMIG9 CVSLgHZOUWEn1y1xuE6QCguFPw0YD4Ff9u4HPsIEmAATSGoCUgAXFRV2W+lGCq4TJII8vzB7SwCB MwE4dsexGx8GrjMTYALuJiCBfxXNzJnl1la4UpAI9tIZPb6WQlzMRg5uffS43kyACZhKQMq3Th6Q fYepeVqcmbC4PNOLyysoux2Qd5meMWfIBJgAE3APgZVCpp/kxnWjYMSuFyRAiryCspfIaWBww/g9 E2ACTCAZCEhgS8AXOKJ4do8Nbm+va6fsdoMXspMsvxSQH+8+xu+YABNgAklBoEZq2qhEECPqrQQY ITU9dCPzy7o0CnwkIfsmxWPIjWQCTCDZCUgJed6Smd1fSxQQCTBCauoKsrzzSd9IGr4mSudwO5gA E2ACLREQErclkhhROxNmhGR02uBxm/6oBQJLAGQax/iVCTABJpBIBKSQjy+Z0f3qRGoTtSVhRkhG x1D484CU4ynorHGMX5kAE2ACCUTglX0COdckUHt2NSXhBIlatrSw+1wJ/IX3KO3qZ37DBJhAQhCQ S8iIizzWJERzmjUi4absgts3NL/0b1Lg3uBj/J4JMAEm4FICnwqZPtzte43CsU/IEZLR4KLCnPsE xP3GZ35lAkyACbiUwKpUKc5IZDGifknoEVLTg6dvnH0cwJUufRC52kyACSQzAYFf/I2BExNlr1G4 rkzoEVJTw2njbPafAbwSDgSfYwJMgAk4kMAGf8A/NBnEiNgnwQip6RHLz5eebaL0VQFR4MCHjqvE BJgAE2hGQJZByNzFM3r80OxEwn5MGkGiHjzyCpnSvrxsuhAYk7A9yg1jAkzA9QRog78mcErRjJzv XN+YKBqQBFN2u2mseFI07oPy8QJi7u6j/I4JMAEm4BwCuhhJLS/ZxIh6IKkEiRpcWDiwoaPcni8l ZjvnEeSaMAEmwAR0ApulRwx2a8TXePswqabsgmHl5kqvZ99NL0PKscHH+T0TYAJMwB4CotTjl3kL Z+Wstqd8+0tNuhGSgby4WPg6BbpRDKWXjWP8ygSYABOwicB6v/SdksxiRNyTVpCo8eR+o5PMpjDo /7PpIeRimQATSHICAuInvyZOLi7s+VOSo0ges+/wHS3FkLFl9wiJv4W/js8yASbABEwkIMU3fnhG FBd23WRirq7NKqlHSLt7TcglM3JuFVL8lR2y7qbC75gAE1BIQOKj+pSGXBaj3YyT1qhhN4I93w0Z WzpRSDwBwLPnGf7EBJgAEzCNwLuZtThn3rycGtNyTICMWJBCdOKQgo2jBASFBc4KcZoPMQEmwARi JiAgnivv0O1K2hcZcyYJeiMLUgsdS5FnRSAwTwBdWriEDzMBJsAEoiQg/7l4Zs4dgJBR3pgUl7Mg henm3Pz1fb3CO19C9g1zGZ9iAkyACbRGwC+Aq4pm5jzV2oXJfJ4FqZXezx1f2tnjxxwAJ7ZyKZ9m AkyACYQiUCGAsUUzcxaEOsnHdhNgK7vdLEK+K34tZ2tqZfVQ3kAbEg8fZAJMIDyBtQGPOIHFKDwk 4yyPkAwSEbzmFZTeCuBfyRS2IwIsfAkTYAIhCYjlnkZx1sI53X4LeZoP7kWABWkvJOEPDMkvPVsI vAigTfgr+SwTYAJJS0DI51N31vxp/vx+9UnLIIaGsyDFAG342E2DAlLOYWOHGODxLUwgsQn4hJA3 Fs3o/p/Ebqaa1rEgxcj1xAm/dEzzpbwK4NQYs+DbmAATSCACehwjTRQUTc8uTqBmWdoUNmqIEfey V/cv7ySzzwBwL7sbihEi38YEEoWAxOfw4WgWo/g6lEdI8fHT7x6aX3a6FPIFAPuYkB1nwQSYgIsI CCEeTdlZdROvF8XfaSxI8TPUcxiev6mXH4GZEDjKpCw5GybABJxNoAoCf1o8I+cVZ1fTPbXjKTuT +mphYbe1qVXVJ9KvJZ7CMwkqZ8MEnEtgpcePo1mMzO0gHiGZy1PPbejYjWdKKZ7hKTwFcDlLJmAz ASnk44GMhhuLn+9VZ3NVEq54FiRFXZo7ZkMPzau9LIBTFBXB2TIBJmAtge1SYuKSwhxyJcZJAQGe slMAlbIsnt1jwz4yOw8Q/wAku5lXxJmzZQIWEVgqJA5nMVJLm0dIavnquQ8bV3pUIACywhtgQXFc BBNgAuYRqBPAbScOyH548mQRMC9bzikUARakUFQUHDsuf31Gpua5FxLXsi88BYA5SyZgNgGJLwFx /uLC7FVmZ835hSbAghSai7KjeQVlgwXw5P+3d26xVVRRGP7/PbW2oqYgSIGC8a6QoPLgJYKppYL1 Fi+p0AcUrw/6oDHxHhU1XuOLiYmJqVGjRvAIGjERQ9GCGNEgmGgUlYhSORRRqQhKy5lZZho1EUs9 9zMz5386ObPXWnutb03yZ86Zvbe2HSoZYgUWgUIJDBj48K8NjQ/rVNdCUebmL0HKjVdRrAeflliz ALBbANQUJaiCiIAIFEzAgA/DPS8CAAAGbElEQVRovF5PRQWjzCuABCkvbMVxap275RQL2AlgWnEi KooIiECeBHYCvGvG5Man9V9RngSL4CZBKgLEQkI0N1uNO7z3ZsIeAFBfSCz5ioAI5EOAS/2Mf0P4 Zmw+3vIpHgEJUvFYFhSp9bJtRwUueIa0mQUFkrMIiEC2BLbB7KYVqQmLsnWQXWkJSJBKyzfH6MaW Oen5ND4OYHSOzjIXARHIjoABfK7GMre+k5r4S3YusioHAQlSOSjnOMfs9p5RGboHAV6vlx5yhCdz ERiewDrS3di1qHHN8GYarQQBCVIlqGc55+BLDz6fBDEjSxeZiYAIDE3gJxrunj5lXKdeWhgaUBSu SpCi0IVhczC2XJ6eS/BRAJOGNdWgCIjAvgQGQDzl9/c/2P3GkX37Dup7tAhIkKLVj/1mE65dGgHv JiNuB9CwX0MNiIAI/E3gdd/827pTEzf+fUGf0SYgQYp2f/6TXXNHerTn2/0ArgN4wH8MdEEEqpwA zT6C5+7QceLxuxEkSPHr2WDGze09x3j0FgDoAKBd22PaR6VdVAJfkLina9G41wFaUSMrWFkISJDK grl0k8zs6JmKjHsI5AWlm0WRRSDCBIjvYbxvlDW+lErRj3CmSu1/CEiQ/gdQXIZb5/SebkFwL4i2 uOSsPEWgQAKbaXx0JH55NpWaMlBgLLlHgIAEKQJNKGYKLe1bTyXtXgDnFzOuYolAhAh8R8MjI7Hj eQlRhLpShFQkSEWAGMUQ4aGAvo87SVys/5ii2CHllAeBrwh7fEfD+Bd1LEQe9GLgIkGKQZMKSfHs 9i3HO8dbYZgHoLaQWPIVgQoR+JjEY9NPHPeGFrVWqANlmlaCVCbQlZ7mnLmbxwfm3QzjdVrHVOlu aP4sCAQElhn4xIpXx72Xhb1MEkBAgpSAJuZSwqx5vSP8Absy3OUYwHG5+MpWBMpAYDdgL4D25IpF TV+XYT5NESECEqQINaOcqSxYYG7lF73nerAbDZgNwCvn/JpLBP5FgPjGjM8EA3s6tcXPv8hU1RcJ UlW1e+hiW9vTkwLyKsKu1n55QzPS1ZIQ6AfwGh07uxY2rtRi1pIwjlVQCVKs2lXaZMOnplVf9s5G YNeSdqG2Jiot7yqO/hkMz9bAf1HnEVXxXTBE6RKkIaDoEjCzY9tY8zPzSV4Dw7FiIgIFEtgF8FWY 37ki1fRhgbHknlACEqSENrZ4ZYXHX2ydQeIKGi41YGTxYitSwgkEAFbC7OXa2rrU2y8ftjPh9aq8 AglIkAoEWE3ubW3fHDhwyIg2g3UQvBBAfTXVr1qzJGBYC+AV52UWLl84KZ2ll8xEABIk3QR5ETjz ou2H1B04cB7Jyww4D8CIvALJKQkEDLCPaG4JAre4a/HYb5NQlGooPwEJUvmZJ27G8PDAg+iFr45f YkAbgTGJK1IF7UsgA9gqI5cGe4PXupc0/bCvgb6LQK4EJEi5EpP9sATa28372UufxoAXwHg+aFOH ddBgnAj8BGAZzN4i6pd1pUb9GqfklWv0CUiQot+jWGfYfOkPTTU1nGVw5xhspp6eYtXO8EiHNQSW +84tH+2PXavzhmLVv9glK0GKXcvim3C4zmn1hvRJ8F2rITgL5HTtqxepfvowrIfDKgZ8NwNvZXfq 8F2RylDJJJqABCnR7Y12ceHPezu89FTzeRaIGQBOBzAh2lknKrvdMKwDsdrI9/v/qFn9wZtjfktU hSomVgQkSLFqV/KTbWnvmeCcdxrMzjDwVAAnAzg0+ZWXvMKMARsIfGLAGs+CNXu3T/i8u5uZks+s CUQgSwISpCxByaxSBIwtc348CgimATaNNihQk7Xn3rD92EmzL438FMT6gO7Tg3cHny1dOv73Yb00 KAIVJiBBqnADNH1+BMJ1ULUH+ZO9wJ9ixAk0d7yZHQ3iaAB1+UWNlZcBCF+13mjARho2mLPPXcAN Xanxm2NViZIVgb8ISJB0KySMgHHW3HSTH7hjBp+iDEcYrYmGpsHvxMSY/AQY7oS9zYAewLYQDD83 G7DJkRsz9f2bup8/ck/CmqdyqpyABKnKb4BqLL95/qa62j21Y3xzjQDHGvwxNDfKYA2ObDCzBpg1 GFy9ozWY8QA4Oxhm9QT/efraz75+vxMIxQTh9gUg+mDYS2CXGcKxPwJaH8k+GnfArC8g+hyw3Rx+ 9PZie+DqtmqNTzXemar5T7boKrYfCqI6AAAAAElFTkSuQmCC"/></symbol><symbol viewBox="0 0 24 24" id="webpack" xmlns="http://www.w3.org/2000/svg"><path d="M19.376 15.988l-7.709 4.45-7.708-4.45V7.087l7.708-4.45 7.709 4.45z" fill="#fff" fill-opacity=".785" stroke-width="0"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18.21 0 .41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.939v2.104h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07l7-3.94zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#1c78c0"/></symbol><symbol viewBox="0 0 24 24" id="wolframlanguage" xmlns="http://www.w3.org/2000/svg"><title>wolframLanguage</title><g transform="scale(.12121)" fill="none" fill-rule="evenodd"><circle cx="99.197" cy="98.946" r="83.28" fill="#212121" stroke-width=".841"/><path d="M182.529 98.828a83.406 83.406 0 0 1-39.14 70.721.064.064 0 0 1-.038.019l-28.62-35.665 23.71 2.612s11.385 1.177 13.978 0c2.373-.938 15.175-18.963 15.175-18.963s-36.75-23.23-49.312-36.032c1.434-21.575-1.656-50.269-1.656-50.03-9.251 9.234-10.429 10.669-19.68 19.203-4.028-13.04-5.923-17.547-9.95-30.588-12.104 9.95-21.337 26.799-27.977 46.48a78.68 78.68 0 0 0-4.23 5.094 109.774 109.774 0 0 0-2.667 3.66 114.558 114.558 0 0 0-5.132 8.002 172.555 172.555 0 0 0-3.403 6.051c-7.706 14.475-14.034 31.066-19.515 46.001a.858.858 0 0 1-.092-.184c-14.988-30.912-9.502-67.85 13.822-93.072 23.325-25.223 59.723-33.575 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z" fill="#e53935"/><path d="M101.452 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.04.477 11.845.238 18.006-.479 32.481-3.55-3.568-10.668-8.074-10.668-8.074zm-27.737 40.778s-6.64-4.029-11.624-4.728c1.435-3.329 5.223-7.596 6.18-8.773-1.913.699-15.653 6.86-17.087 12.084a74.804 74.804 0 0 1 11.385 3.79 35.993 35.993 0 0 0-8.774 20.158s21.815-3.33 38.185-1.196c.283.168.609.251.938.24l8.534.239 27.111 45.136.221.35c-.037.018-.055.037-.073.037-51.133 18.485-88.085-15.543-95.976-27.443.034-.102.058-.206.074-.313 7.1-30.017 15.855-65.939 30-76.552 7.356-12.82 9.49-31.783 22.751-41.734 3.33 9.951 8.553 30.588 12.103 40.539 15.653 15.652 39.361 35.094 55.234 43.15 1.656.956 3.79 7.596 3.79 7.596l-6.401 8.056-68.276-6.879a54.462 54.462 0 0 0-4.58-.183 86.848 86.848 0 0 0-14.144 1.36c3.311-8.295 10.43-14.935 10.43-14.935zm22.054-8.774c3.789-.46 7.817.956 12.323 3.568 4.267-1.195 4.745-1.434 9.013-2.612-5.463-4.028-11.386-8.295-19.442-7.118a47.249 47.249 0 0 0-1.894 6.162z" fill="#fff" stroke-width=".936"/></g></symbol><symbol viewBox="0 0 24 24" id="word" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2h1z" fill="#01579b"/></symbol><symbol viewBox="0 0 24 24" id="xaml" xmlns="http://www.w3.org/2000/svg"><path d="M18.93 12l-3.47 6H8.54l-3.47-6 3.47-6h6.92l3.47 6m4.84 0l-4.04 7L18 18l3.46-6L18 6l1.73-1 4.04 7M.23 12l4.04-7L6 6l-3.46 6L6 18l-1.73 1-4.04-7z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="xml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="yaml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="yang" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 289.99999 290.00001" id="yarn" xmlns="http://www.w3.org/2000/svg"><path d="M250.733 218.418c-12.39 2.943-18.661 5.653-33.993 15.641-24.004 15.487-50.176 22.688-50.176 22.688s-2.168 3.252-8.44 4.723c-10.84 2.633-51.647 4.878-55.364 4.956-9.988.077-16.105-2.555-17.809-6.66-5.188-12.388 7.434-17.809 7.434-17.809s-2.788-1.703-4.414-3.252c-1.471-1.47-3.02-4.413-3.484-3.33-1.936 4.724-2.943 16.261-8.13 21.45-7.125 7.2-20.598 4.8-28.573.619-8.75-4.646.62-15.564.62-15.564s-4.724 2.788-8.518-2.942c-3.407-5.266-6.582-14.248-5.73-25.32 1.084-12.777 15.176-25.011 15.176-25.011s-2.477-18.661 5.653-37.787c7.356-17.422 27.179-31.437 27.179-31.437s-16.648-18.352-10.454-35c4.027-10.84 5.653-10.763 6.97-11.227 4.645-1.781 9.136-3.717 12.466-7.356 16.648-17.964 37.864-14.557 37.864-14.557s9.911-30.431 19.203-24.469c2.865 1.859 13.163 24.778 13.163 24.778s10.996-6.426 12.235-4.026c6.659 12.931 7.433 37.632 4.49 52.654-4.955 24.778-17.344 38.096-22.3 46.459-1.161 1.936 13.319 8.053 22.456 33.373 8.44 23.152.929 42.587 2.245 44.756.232.387.31.542.31.542s9.679.774 29.114-11.228c10.376-6.427 22.688-13.628 36.703-13.783 13.55-.232 14.247 15.719 4.104 18.12z" fill="#2c8ebb" stroke-width=".774"/></symbol><symbol viewBox="0 0 24 24" id="zip" xmlns="http://www.w3.org/2000/svg"><path d="M14 17h-2v-2h-2v-2h2v2h2m0-6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2m5-4H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#afb42b"/></symbol></svg>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" id="actionscript" xmlns="http://www.w3.org/2000/svg"><text style="line-height:113.99999857%" x="5.605" y="15.892" transform="scale(.91325 1.095)" font-weight="400" font-size="42.822" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/><path style="line-height:125%" d="M4.744 2.031c-1.157 0-1.994.31-2.51.93-.515.612-.771 1.678-.771 3.197v2.467c0 1.408-.402 2.111-1.201 2.111v2.035c.8 0 1.2.679 1.2 2.036v2.654c0 1.512.26 2.562.78 3.152.52.59 1.355.885 2.502.885V19.43c-.447 0-.77-.151-.97-.453-.195-.303-.292-.815-.292-1.538v-2.267c0-1.807-.404-2.937-1.214-3.395v-.045c.81-.464 1.214-1.581 1.214-3.351V6.025c0-1.283.42-1.925 1.262-1.925V2.03zm14.66 0V4.1c.842 0 1.262.642 1.262 1.925v2.268c0 1.843.402 2.996 1.207 3.46v.046c-.805.442-1.207 1.544-1.207 3.306v2.356c0 .715-.099 1.22-.299 1.516-.2.302-.52.453-.963.453v2.068c1.152 0 1.984-.295 2.494-.885.516-.59.772-1.663.772-3.218V14.84c0-1.379.404-2.069 1.209-2.069v-2.035c-.805 0-1.21-.696-1.21-2.09V6.113c0-1.49-.255-2.54-.77-3.152-.516-.62-1.348-.93-2.495-.93zm-3.054 4.46c-.455 0-.886.057-1.293.173a3.056 3.056 0 0 0-1.078.527c-.308.241-.551.549-.731.924-.18.37-.27.817-.27 1.336 0 .663.165 1.227.493 1.695.33.468.831.864 1.502 1.188.263.125.509.249.736.37.227.12.422.244.586.374.168.13.299.271.394.424a.963.963 0 0 1 .145.521c0 .144-.03.28-.09.405a.9.9 0 0 1-.275.318c-.12.088-.272.158-.455.21a2.34 2.34 0 0 1-.635.075c-.415 0-.825-.083-1.233-.25a3.644 3.644 0 0 1-1.13-.763v2.222a3.68 3.68 0 0 0 1.101.418c.427.093.875.139 1.346.139.459 0 .894-.05 1.305-.152a3.002 3.002 0 0 0 1.09-.5c.31-.237.556-.543.736-.918.183-.38.275-.849.275-1.405 0-.403-.052-.755-.156-1.056a2.542 2.542 0 0 0-.45-.813 3.295 3.295 0 0 0-.704-.633 6.754 6.754 0 0 0-.922-.535 12.4 12.4 0 0 1-.676-.348c-.2-.115-.37-.231-.51-.347a1.502 1.502 0 0 1-.322-.375.91.91 0 0 1-.115-.453c0-.153.033-.288.101-.408a.948.948 0 0 1 .29-.32c.123-.089.275-.156.454-.202a2.18 2.18 0 0 1 .598-.078c.16 0 .326.015.502.043.18.028.36.07.539.13.18.056.354.13.522.218.171.088.329.188.472.304V6.871a4.039 4.039 0 0 0-.957-.285 6.448 6.448 0 0 0-1.185-.096zm-8.774.165l-3.123 9.967h2.094l.605-2.217h3.053l.61 2.217h2.107L9.869 6.656H7.576zm1.072 1.78h.047c.028.347.077.646.145.896l.922 3.35H7.564l.934-3.377c.08-.288.13-.578.15-.87z" font-weight="400" font-size="51.019" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="android" xmlns="http://www.w3.org/2000/svg"><path d="M15 5h-1V4h1m-5 1H9V4h1m5.53-1.84L16.84.85c.19-.19.19-.51 0-.71a.513.513 0 0 0-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.14a.501.501 0 0 0-.7 0c-.2.2-.2.52 0 .71l1.31 1.31C6.97 3.26 6 5 6 7h12c0-2-1-3.75-2.47-4.84M20.5 8A1.5 1.5 0 0 0 19 9.5v7a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 20.5 8m-17 0A1.5 1.5 0 0 0 2 9.5v7A1.5 1.5 0 0 0 3.5 18 1.5 1.5 0 0 0 5 16.5v-7A1.5 1.5 0 0 0 3.5 8M6 18a1 1 0 0 0 1 1h1v3.5A1.5 1.5 0 0 0 9.5 24a1.5 1.5 0 0 0 1.5-1.5V19h2v3.5a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5V19h1a1 1 0 0 0 1-1V8H6v10z" fill="#c0ca33"/></symbol><symbol viewBox="0 0 24 24" id="angular" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="angular-component" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#0288d1"/></symbol><symbol viewBox="0 0 24 24" id="angular-directive" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ab47bc"/></symbol><symbol viewBox="0 0 24 24" id="angular-guard" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-pipe" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#00897b"/></symbol><symbol viewBox="0 0 24 24" id="angular-resolver" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-routing" xmlns="http://www.w3.org/2000/svg"><path d="M11 10H5L3 8l2-2h6V3l1-1 1 1v1h6l2 2-2 2h-6v2h6l2 2-2 2h-6v6a2 2 0 0 1 2 2H9a2 2 0 0 1 2-2V10z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-service" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ffca28"/></symbol><symbol viewBox="0 0 100 100" id="apiblueprint" xmlns="http://www.w3.org/2000/svg"><title>api-blueprint</title><path d="M50.133 7.521A16.998 16.998 0 0 0 33.135 24.52a16.998 16.998 0 0 0 4.945 11.974L24.861 57.398a16.998 16.998 0 0 0-3.175-.308A16.998 16.998 0 0 0 4.688 74.088a16.998 16.998 0 0 0 16.998 16.998 16.998 16.998 0 0 0 16.998-16.998 16.998 16.998 0 0 0-7.063-13.773l12.576-19.89a16.998 16.998 0 0 0 5.936 1.093 16.998 16.998 0 0 0 6.154-1.155l12.537 19.83a16.998 16.998 0 0 0-7.244 13.895 16.998 16.998 0 0 0 16.998 17 16.998 16.998 0 0 0 16.998-17A16.998 16.998 0 0 0 78.578 57.09a16.998 16.998 0 0 0-2.95.262L62.337 36.327A16.998 16.998 0 0 0 67.13 24.52 16.998 16.998 0 0 0 50.132 7.522z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="applescript" xmlns="http://www.w3.org/2000/svg"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" fill="#78909c"/></symbol><symbol viewBox="0 0 24 24" id="appveyor" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-.084 0-.165.008-.248.01a10 10 0 0 0-.266.01 9.952 9.952 0 0 0-.754.066 10 10 0 0 0-.148.018 9.855 9.855 0 0 0-.93.177 10 10 0 0 0-.07.02c-.196.049-.392.1-.584.16v.012a10 10 0 0 0-2 .875V3.34c-.02.012-.038.027-.059.039a10 10 0 0 0-.953.635c-.09.067-.172.142-.26.213a10 10 0 0 0-.628.546c-.109.104-.211.211-.315.319a10 10 0 0 0-.476.539c-.1.12-.201.237-.295.361a10 10 0 0 0-.52.766c-.088.143-.17.288-.252.435a10 10 0 0 0-.363.723c-.072.161-.136.327-.2.492a10 10 0 0 0-.269.778c-.02.067-.044.131-.062.199a10 10 0 0 0-.008.027c-.098.364-.166.728-.22 1.09-.012.077-.024.153-.034.23a9.85 9.85 0 0 0-.08 1.182c0 .03-.006.057-.006.086a10 10 0 0 0 .008.148c.001.094-.002.188.002.282l.011.004a10 10 0 0 0 .333 2.158l-.012-.004c.012.047.033.091.047.139a10 10 0 0 0 .322.955c.02.052.037.106.059.158a10 10 0 0 0 .503 1.035c.065.116.14.226.21.34a10 10 0 0 0 .423.64c.092.128.187.252.285.375a10 10 0 0 0 .448.52c.112.123.222.248.341.365a10 10 0 0 0 .803.719 10 10 0 0 0 .01.006c.099.078.207.146.309.22a10 10 0 0 0 .648.442c.138.085.28.163.424.242a10 10 0 0 0 .715.358c.114.051.226.106.343.154a10 10 0 0 0 1.133.389c.016.004.031.01.047.015a10 10 0 0 0 .461.098 10 10 0 0 0 .482.103 10 10 0 0 0 .418.051 10 10 0 0 0 .575.065 10 10 0 0 0 .144.005A10 10 0 0 0 12 22a10 10 0 0 0 .197-.01 10 10 0 0 0 .496-.025 10 10 0 0 0 .49-.043 10 10 0 0 0 .489-.074 10 10 0 0 0 .51-.098 10 10 0 0 0 .47-.12 10 10 0 0 0 .477-.14 10 10 0 0 0 .47-.172 10 10 0 0 0 .481-.197 10 10 0 0 0 .414-.201 10 10 0 0 0 .475-.252 10 10 0 0 0 .39-.238 10 10 0 0 0 .452-.301 10 10 0 0 0 .38-.291 10 10 0 0 0 .385-.315 10 10 0 0 0 .375-.347 10 10 0 0 0 .36-.363 10 10 0 0 0 .293-.334 10 10 0 0 0 .353-.434 10 10 0 0 0 .28-.393 10 10 0 0 0 .263-.4 10 10 0 0 0 .264-.461 10 10 0 0 0 .228-.436 10 10 0 0 0 .195-.437 10 10 0 0 0 .196-.48 10 10 0 0 0 .228-.69 10 10 0 0 0 .028-.094 10 10 0 0 0 .021-.066 10 10 0 0 0 .098-.461 10 10 0 0 0 .103-.482 10 10 0 0 0 .051-.418 10 10 0 0 0 .065-.575 10 10 0 0 0 .005-.144A10 10 0 0 0 22 12a10 10 0 0 0-.01-.197 10 10 0 0 0-.025-.496 10 10 0 0 0-.043-.49 10 10 0 0 0-.074-.489 10 10 0 0 0-.098-.51 10 10 0 0 0-.12-.47 10 10 0 0 0-.14-.477 10 10 0 0 0-.172-.47 10 10 0 0 0-.197-.481 10 10 0 0 0-.201-.414 10 10 0 0 0-.252-.475 10 10 0 0 0-.238-.39 10 10 0 0 0-.301-.452 10 10 0 0 0-.291-.38 10 10 0 0 0-.315-.385 10 10 0 0 0-.347-.375 10 10 0 0 0-.363-.36 10 10 0 0 0-.334-.293 10 10 0 0 0-.434-.353 10 10 0 0 0-.393-.28 10 10 0 0 0-.4-.263 10 10 0 0 0-.461-.264 10 10 0 0 0-.436-.228 10 10 0 0 0-.437-.196 10 10 0 0 0-.48-.195 10 10 0 0 0-.69-.228 10 10 0 0 0-.094-.028 10 10 0 0 0-.066-.021 10 10 0 0 0-.461-.098 10 10 0 0 0-.482-.103 10 10 0 0 0-.418-.051 10 10 0 0 0-.575-.065 10 10 0 0 0-.144-.005A10 10 0 0 0 12 2zm-.016 5.002a5 5 0 0 1 .262.01 5 5 0 0 1 .227.011 5 5 0 0 1 .341.05 5 5 0 0 1 .135.019 5 5 0 0 1 .014.004 5 5 0 0 1 .115.025 5 5 0 0 1 .303.076 5 5 0 0 1 .265.086 5 5 0 0 1 .2.074 5 5 0 0 1 .242.106 5 5 0 0 1 .228.11 5 5 0 0 1 .196.109 5 5 0 0 1 .244.15 5 5 0 0 1 .17.12 5 5 0 0 1 .224.171 5 5 0 0 1 .186.16 5 5 0 0 1 .176.164 5 5 0 0 1 .172.18 5 5 0 0 1 .177.203 5 5 0 0 1 .133.172 5 5 0 0 1 .16.223 5 5 0 0 1 .133.214 5 5 0 0 1 .12.21 5 5 0 0 1 .107.216 5 5 0 0 1 .109.24 5 5 0 0 1 .084.223 5 5 0 0 1 .08.242 5 5 0 0 1 .07.264 5 5 0 0 1 .047.207 5 5 0 0 1 .045.277 5 5 0 0 1 .028.227 5 5 0 0 1 .02.351 5 5 0 0 1 .003.079 5 5 0 0 1-.012.271 5 5 0 0 1-.011.227 5 5 0 0 1-.05.341 5 5 0 0 1-.019.135 5 5 0 0 1-.004.014 5 5 0 0 1-.025.115 5 5 0 0 1-.076.303 5 5 0 0 1-.086.265 5 5 0 0 1-.074.2 5 5 0 0 1-.106.242 5 5 0 0 1-.11.228 5 5 0 0 1-.109.196 5 5 0 0 1-.15.244 5 5 0 0 1-.12.17 5 5 0 0 1-.171.224 5 5 0 0 1-.16.186 5 5 0 0 1-.164.176 5 5 0 0 1-.18.172 5 5 0 0 1-.203.177l-.002.002c-.018.019-.028.035-.047.053l-3.959 5.09-3.05-.979a141.684 141.684 0 0 0 3.177-3.084 5 5 0 0 1-.103-.015 5 5 0 0 1-.149-.024 5 5 0 0 1-.115-.025 5 5 0 0 1-3.57-3.04 5.072 5.072 0 0 1-.206-.661 5 5 0 0 1-.033-.147c-.025-.118-.036-.24-.054-.36-.987.993-1.964 1.993-2.954 3.05l-.98-3.053 5.092-3.957c.043-.044.082-.07.125-.11a5 5 0 0 1 .71-.634c.18-.13.367-.25.561-.356a5 5 0 0 1 .16-.08 4.94 4.94 0 0 1 .516-.222 5 5 0 0 1 .147-.057c.211-.07.43-.123.654-.164a5 5 0 0 1 .172-.027c.236-.035.476-.058.722-.059zM12 9a3 3 0 0 0-.053.002 3 3 0 0 0-.166.01 3 3 0 0 0-.133.011 3 3 0 0 0-.17.026 3 3 0 0 0-.113.021 3 3 0 0 0-.19.05 3 3 0 0 0-.103.03 3 3 0 0 0-.16.057 3 3 0 0 0-.129.053 3 3 0 0 0-.146.072 3 3 0 0 0-.12.063 3 3 0 0 0-.132.082 3 3 0 0 0-.123.08 3 3 0 0 0-.116.088 3 3 0 0 0-.126.105 3 3 0 0 0-.1.094 3 3 0 0 0-.111.111 3 3 0 0 0-.096.107 3 3 0 0 0-.094.116 3 3 0 0 0-.098.136 3 3 0 0 0-.072.11 3 3 0 0 0-.076.133 3 3 0 0 0-.07.132 3 3 0 0 0-.063.14 3 3 0 0 0-.054.14 3 3 0 0 0-.077.228 3 3 0 0 0-.007.026 3 3 0 0 0-.03.138 3 3 0 0 0-.031.149 3 3 0 0 0-.014.11 3 3 0 0 0-.02.183 3 3 0 0 0-.001.052A3 3 0 0 0 9 12a3 3 0 0 0 .002.053 3 3 0 0 0 .01.166 3 3 0 0 0 .011.133 3 3 0 0 0 .026.17 3 3 0 0 0 .021.113 3 3 0 0 0 .05.19 3 3 0 0 0 .03.103 3 3 0 0 0 .057.16 3 3 0 0 0 .053.129 3 3 0 0 0 .072.146 3 3 0 0 0 .063.12 3 3 0 0 0 .082.132 3 3 0 0 0 .08.123 3 3 0 0 0 .088.116 3 3 0 0 0 .105.126 3 3 0 0 0 .094.1 3 3 0 0 0 .111.111 3 3 0 0 0 .107.096 3 3 0 0 0 .116.094 3 3 0 0 0 .136.098 3 3 0 0 0 .11.072 3 3 0 0 0 .133.076 3 3 0 0 0 .132.07 3 3 0 0 0 .135.06 3 3 0 0 0 .153.061 3 3 0 0 0 .216.07 3 3 0 0 0 .004.003 3 3 0 0 0 .026.007 3 3 0 0 0 .138.03 3 3 0 0 0 .149.031 3 3 0 0 0 .11.014 3 3 0 0 0 .183.02 3 3 0 0 0 .011.001 3 3 0 0 0 .041 0A3 3 0 0 0 12 15a3 3 0 0 0 .053-.002 3 3 0 0 0 .166-.01 3 3 0 0 0 .133-.011 3 3 0 0 0 .17-.026 3 3 0 0 0 .113-.021 3 3 0 0 0 .19-.05 3 3 0 0 0 .103-.03 3 3 0 0 0 .16-.057 3 3 0 0 0 .129-.053 3 3 0 0 0 .146-.072 3 3 0 0 0 .12-.063 3 3 0 0 0 .132-.082 3 3 0 0 0 .123-.08 3 3 0 0 0 .116-.088 3 3 0 0 0 .126-.105 3 3 0 0 0 .1-.094 3 3 0 0 0 .111-.111 3 3 0 0 0 .096-.107 3 3 0 0 0 .094-.116 3 3 0 0 0 .098-.136 3 3 0 0 0 .072-.11 3 3 0 0 0 .076-.133 3 3 0 0 0 .07-.132 3 3 0 0 0 .06-.135 3 3 0 0 0 .061-.153 3 3 0 0 0 .07-.216 3 3 0 0 0 .003-.004 3 3 0 0 0 .007-.026 3 3 0 0 0 .03-.138 3 3 0 0 0 .031-.149 3 3 0 0 0 .002-.008 3 3 0 0 0 .012-.101 3 3 0 0 0 .02-.184 3 3 0 0 0 .001-.011 3 3 0 0 0 0-.041A3 3 0 0 0 15 12a3 3 0 0 0-.002-.053 3 3 0 0 0-.01-.166 3 3 0 0 0-.011-.133 3 3 0 0 0-.026-.17 3 3 0 0 0-.021-.113 3 3 0 0 0-.05-.19 3 3 0 0 0-.03-.103 3 3 0 0 0-.057-.16 3 3 0 0 0-.053-.129 3 3 0 0 0-.072-.146 3 3 0 0 0-.063-.12 3 3 0 0 0-.082-.132 3 3 0 0 0-.08-.123 3 3 0 0 0-.088-.116 3 3 0 0 0-.105-.126 3 3 0 0 0-.094-.1 3 3 0 0 0-.111-.111 3 3 0 0 0-.107-.096 3 3 0 0 0-.116-.094 3 3 0 0 0-.136-.098 3 3 0 0 0-.11-.072 3 3 0 0 0-.133-.076 3 3 0 0 0-.132-.07 3 3 0 0 0-.14-.063 3 3 0 0 0-.14-.054 3 3 0 0 0-.228-.077 3 3 0 0 0-.026-.007 3 3 0 0 0-.138-.03 3 3 0 0 0-.149-.031 3 3 0 0 0-.008-.002 3 3 0 0 0-.101-.012 3 3 0 0 0-.184-.02 3 3 0 0 0-.011-.001 3 3 0 0 0-.041 0A3 3 0 0 0 12 9z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 720 720" id="arduino" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="ana" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-opacity="100%" stroke-width="60" stroke="#00979c" d="M174 30a10.5 10.1 0 0 0 0 280C364 320 344 30 544 30a10.5 10.1 0 0 1 0 280C354 320 374 30 174 30"/><path d="M528 205v-32.8h-32.5v-13.7H528V126h13.9v32.5h32.5v13.7h-32.5V205H528z" text-anchor="middle" fill="#00979c" stroke-width="20" stroke="#00979c" font-family="sans-serif" font-size="167"/><path fill="#00979c" stroke="#00979c" stroke-width="23.6" transform="matrix(1.56 0 0 .64 -366 .528)" d="M321 266v-17.4h53.3V266H321z"/></symbol></defs><title>Layer 1</title><use x="20.063" y="360.85" transform="matrix(.997 0 0 .997 -18.596 -159.19)" xlink:href="#ana"/></symbol><symbol viewBox="0 0 24 24" id="assembly" xmlns="http://www.w3.org/2000/svg"><path d="M1.746 1.566v20.905H5.13v-2.088H3.438V3.656h1.69v-2.09H1.747zm17.219 0v2.09h1.693v16.727h-1.693v2.09h3.383V1.566h-3.383zM15.196 3.988c-.5 0-.93.076-1.29.225-.359.15-.652.372-.877.671-.226.302-.39.673-.494 1.108a6.715 6.715 0 0 0-.155 1.54c0 .573.049 1.083.15 1.528.1.442.264.811.49 1.11.222.298.512.524.872.676.36.153.795.23 1.304.23.518 0 .954-.075 1.308-.224.353-.153.643-.376.869-.671.219-.29.38-.661.484-1.112.104-.454.156-.967.156-1.54 0-.573-.052-1.079-.152-1.515a2.92 2.92 0 0 0-.485-1.106 2.09 2.09 0 0 0-.868-.686c-.354-.155-.79-.234-1.312-.234zm-6.814.12a.941.941 0 0 1-.138.458.849.849 0 0 1-.356.296A1.71 1.71 0 0 1 7.385 5a5.244 5.244 0 0 1-.631.037v1.11H8.19v3.6H6.754v1.188h4.545V9.745H9.894V4.11H8.382zm6.814 1.138c.375 0 .643.176.805.527.161.348.241.933.241 1.756 0 .814-.082 1.399-.247 1.756-.164.356-.43.534-.799.534-.369 0-.636-.178-.8-.534-.165-.357-.248-.941-.248-1.749 0-.829.082-1.415.243-1.763.162-.35.43-.527.805-.527zm-6.33 7.64c-.5 0-.93.073-1.29.223-.359.15-.651.374-.877.673-.225.302-.39.67-.494 1.106a6.715 6.715 0 0 0-.155 1.54c0 .573.05 1.082.15 1.527.1.442.264.814.49 1.112.222.3.514.525.874.677.36.152.793.229 1.302.229.519 0 .954-.076 1.308-.225.354-.153.643-.376.869-.672.22-.29.38-.66.484-1.111.104-.455.156-.967.156-1.54 0-.573-.05-1.079-.15-1.515a2.923 2.923 0 0 0-.487-1.106 2.084 2.084 0 0 0-.867-.686c-.353-.156-.791-.232-1.313-.232zm5.846.119a.941.941 0 0 1-.138.457.85.85 0 0 1-.356.296 1.71 1.71 0 0 1-.503.137 5.245 5.245 0 0 1-.631.037v1.112h1.435v3.597h-1.435v1.189h4.545v-1.189h-1.405v-5.636h-1.512zm-5.846 1.137c.375 0 .643.176.805.527.162.347.241.933.241 1.756 0 .813-.08 1.399-.245 1.755-.164.357-.432.534-.8.534-.37 0-.637-.177-.802-.534-.164-.356-.245-.939-.245-1.746 0-.83.08-1.418.242-1.765.161-.35.43-.527.804-.527z" fill="#ff6e40"/></symbol><symbol viewBox="0 0 24 24" id="aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="api" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apa"/><linearGradient id="apa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#apb"/><linearGradient id="apb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#apc"/><linearGradient id="apc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#apd"/><linearGradient id="apd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#ape"/><linearGradient id="ape" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#apf"/><linearGradient id="apf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apg"/><linearGradient id="apg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="app" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#aph"/><linearGradient id="aph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><g transform="rotate(11.282 -1.694 21.569) scale(.47102)" clip-rule="evenodd" fill="none" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#api)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#apj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#apk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#apl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#apm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#apn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#apo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#app)"/></g></symbol><symbol viewBox="0 0 24 24" id="autohotkey" xmlns="http://www.w3.org/2000/svg"><path d="M5 3c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm3.668 3.447a.9.9 0 0 1 .652.256.84.84 0 0 1 .262.625c0 .34-.014.852-.041 1.537-.022.68-.033 1.19-.033 1.53 0 .111-.016.326-.047.644a6.149 6.149 0 0 0-.033.68l2.578-.485c1.007-.179 1.874-.281 2.603-.308.018-.3.048-1.105.088-2.416.01-.345.115-.742.317-1.19.25-.55.533-.826.851-.826.237 0 .448.08.631.236.197.17.295.382.295.637a.775.775 0 0 1-.025.201c-.09.327-.135.612-.135.854 0 .125-.014.32-.041.584-.023.26-.033.453-.033.578 0 .425-.022 1.056-.067 1.893a38.963 38.963 0 0 0-.068 1.892c0 .327.025.816.074 1.465.05.649.074 1.136.074 1.463a.84.84 0 0 1-.261.625.893.893 0 0 1-.65.254 1 1 0 0 1-.686-.254.777.777 0 0 1-.29-.611c0-.327-.015-.818-.046-1.471a39.552 39.552 0 0 1-.041-1.47c0-.256.004-.482.013-.679-.702.032-1.57.142-2.603.33-.86.157-1.719.316-2.578.477-.01.304-.042.812-.096 1.523a22.354 22.354 0 0 0-.066 1.538.84.84 0 0 1-.262.625.893.893 0 0 1-.65.253.898.898 0 0 1-.653-.253.84.84 0 0 1-.262-.625c0-.452.038-1.128.114-2.028.08-.9.12-1.575.12-2.027 0-.573.015-1.436.042-2.586.027-1.155.04-2.017.04-2.59a.84.84 0 0 1 .263-.625.895.895 0 0 1 .65-.256z" fill="#4caf50"/></symbol><symbol viewBox="0 0 24 24" id="autoit" xmlns="http://www.w3.org/2000/svg"><defs id="ardefs8"><style id="arstyle4482">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style><style id="arstyle4510">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style></defs><g id="arg4522" transform="translate(-59.538 -26.404) scale(.0555)"><path d="M12.8 2.133A10.666 10.666 0 0 0 2.136 12.799 10.666 10.666 0 0 0 12.8 23.465a10.666 10.666 0 0 0 10.668-10.666A10.666 10.666 0 0 0 12.8 2.133zm.15 4.713c.456 0 .836.105 1.142.314.306.21.565.469.78.78l6.089 8.812H9.627l1.82-2.506h3.36c.315 0 .589.01.822.03a11.93 11.93 0 0 1-.473-.663 39.13 39.13 0 0 0-.517-.75l-1.748-2.578-4.577 6.467H4.746l6.25-8.813c.204-.281.46-.534.772-.757.31-.224.705-.336 1.181-.336z" transform="matrix(16.89188 0 0 16.89188 1072.761 475.745)" id="arcircle4514" fill="#1976d2" stroke-width=".026"/></g></symbol><symbol viewBox="0 0 213.33333 213.33333" id="babel" xmlns="http://www.w3.org/2000/svg"><path d="M50.22 199.659c-.875-.406-1.261-1.6-.857-2.652.404-1.053.12-1.914-.63-1.914s-1.615.748-1.92 1.663c-.328.983-1.27.302-2.304-1.667-.962-1.831-3.718-5.533-6.126-8.226-9.418-10.535-7.71-27.444 5.432-53.77 12.459-24.96 23.117-39.033 45.966-60.696 30.229-28.66 52.679-46.223 70.587-55.22 10.98-5.518 13.025-5.059 2.778.624-11.004 6.102-11.378 6.359-10.512 7.226.33.33 7.306-2.67 15.504-6.667 15.87-7.737 16.34-7.912 16.34-6.082 0 .652-4.95 3.738-11 6.858-13.062 6.736-12.722 6.48-10.472 7.872 1.117.69 5.428-.582 11.54-3.406 5.367-2.48 10.397-4.508 11.179-4.508 2.755 0-3.928 5.302-11.541 9.157-20.437 10.35-68.937 46.043-68.07 50.097.166.777-5.792 7.639-13.241 15.248-15.257 15.587-26.14 30.002-33.748 44.706-6.379 12.326-7.457 17.734-5.385 26.996 3.482 15.56 11.592 18.366 31.482 10.895 28.228-10.603 45.758-28.704 47.022-48.556.602-9.442-1.317-13.479-8.52-17.93-4.01-2.48-5.268-2.621-12.065-1.365-4.173.771-10.153 2.906-13.289 4.744s-6.455 3.34-7.377 3.34c-.922 0-3.216 1.336-5.096 2.968-1.88 1.633.48-1.13 5.247-6.14 6.82-7.167 7.956-8.9 5.333-8.132-5.208 1.525-10.194 4.33-15.649 8.803-2.76 2.264-.923.175 4.08-4.641 11.565-11.131 21.183-15.97 33.088-16.641 17.097-.966 27.254 5.805 31.964 21.31 2.435 8.017 2.609 10.24 1.353 17.37-1.65 9.361-7.034 21.553-15.593 35.307-4.398 7.067-8.434 11.427-15.588 16.844-9.166 6.94-15.654 11.02-15.654 9.845 0-.295 2.455-2.161 5.455-4.147 8.818-5.835 5.075-5.377-8.326 1.02-6.854 3.27-15.199 6.593-18.542 7.38-7.106 1.675-30.527 3.164-32.846 2.089zm-8.408-19.899c0-1.1-.6-2-1.333-2-.734 0-1.334.9-1.334 2s.6 2 1.333 2c.734 0 1.334-.9 1.334-2zm89.255-8.204c1.53-1.945 2.473-3.845 2.097-4.222-.377-.377-.836-.435-1.02-.13-.182.306-1.787 2.206-3.565 4.223-1.778 2.016-2.571 3.666-1.763 3.666s2.72-1.591 4.25-3.536zm-77.644-1.745c-.82-2.172-1.74-3.7-2.045-3.396-.951.952 1.088 7.345 2.343 7.345.656 0 .522-1.777-.298-3.95zm82.303-27.915c-.837-.837-3.217 2.55-3.184 4.53.012.734.896.178 1.965-1.235 1.07-1.413 1.618-2.896 1.219-3.295zm-66.238-36.904c-1.312-1.312-3.676.702-3.676 3.133 0 2.035.175 2.031 2.254-.047 1.24-1.24 1.88-2.628 1.422-3.086zm39.657.768c4.403-2.196 6.8-3.986 5.333-3.982-2.838.01-16.667 6.028-16.667 7.254 0 1.6 3.717.527 11.333-3.272zm16.667-5.333c0-.733-.9-1.333-2-1.333s-2 .6-2 1.333.9 1.333 2 1.333 2-.6 2-1.333zm-3.334-3.923l5.334-1.104-7.334-.133c-4.033-.073-8.233.45-9.333 1.16-2.539 1.64 3.572 1.682 11.333.077zm35.738-63.976c2.788-1.69 4.765-3.376 4.393-3.748-.947-.947-11.942 5.654-14.237 8.548-1.792 2.258-1.714 2.276 1.44.329a1452.76 1452.76 0 0 1 8.403-5.13z" fill="#ffca28" stroke-width="1.333"/></symbol><symbol viewBox="0 0 400 400" fill-opacity=".05" id="bithound" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.88 0 0 .88 24.121 2.895)" fill="#e53935" fill-opacity="1"><path d="M370.5 207c-1.5-14.8-4.8-29.9-9.5-44-13.5-40.3-38.6-81.6-70.3-110.1-1.4-1.2-6.7-4.4-8.7-3.3-5.2 2.9 4.6 22.8 5.8 26.4 7.4 22 12.1 45.3 6.8 68.3-7.1 30.4-30.4 51.7-61.5 54.3-17.1 1.4-34.3-.5-51.4 1.5-25.6 3-51.7 11.8-68 32.8-1.9 2.4-3.6 5.1-5.2 7.9h-.4c-6.3.7-12.6-2-15.7-3.7-.8-.5-1.6-.9-2.2-1.2-19-10.5-33-34-41.6-53.4-3.9-9-7.2-18.4-9.3-27.9-1-4.3-1.1-8.8-1.3-13.2-.1-2.7.3-6.5-1.2-8.9-3.3-5.2-7.5-.2-8.2 4-1.1 6.9-2.1 13.7-1.8 20.7.5 11.8 3.8 23.5 8 34.5 6.2 16.2 14.9 31.1 26.2 44.4 4.7 5.5 9.7 10.6 15.1 15.3 4.8 4.3 10.9 7.7 14.5 13.2 4.2 6.3 4.9 14.1 4.5 21.4-1 19.3-1.6 37.4 3.9 56.2 4.8 16.7 10.8 33.8 20.8 48.1 5 7.1 11.2 14.6 18 19.9 4.6 3.6 13.3 4 8.3-9.2-11.1-29.3-12.1-59.7 5.2-87.1 14.5-22.8 40.1-43.1 69-39.5 42.5 5.3 72.1 44.3 70 86-.6 11.7-1 21.7-4.7 32.7-1.5 4.4-2.6 10-1.5 14.6 1.8 7.8 10.5 4.9 14.3-.2 10.3-14 21.1-27.6 30.8-42 31.6-47.2 47-101.8 41.3-158.5z"/><path d="M132.4 92.1c.7 2.3 1.4 4.8 1.9 7.5.1 1.1.4 2.3 1 3.4 2.6 6.8 8.9 10.5 14.8 14 3.6 2.2 10.1 4.3 14.1 5.9 5.2 2.1 16.4-.6 21.7-1 12.2-1 23.5-5.3 34.7 1.2-57.4 67.3-3.2 82.3 38.8 49.9 48-37 2.8-124.3 2.8-124.3s-1-6.8-19.2-10.8c-1.7-.9-3.4-1.7-5.1-2.4-18-8.3-34.2 5.3-47.2 16.4-3.8 3.2-7.5 6.4-11.5 9.4-5.4 4-11.2 7.3-17.3 10.2-6.4 3-14 6.4-21.1 6.7-1 0-2.9.2-4.9.6-3.1.3-4.7 1.1-5.4 2.5-1.2 1-2 2.4-1.8 4.2.2 2.5 1.4 4.6 2.7 6.2.4.1.7.3 1 .4z"/></g></symbol><symbol viewBox="0 0 400.00001 399.99999" id="bower" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12.061 33.203) scale(.81733)"><path d="M447.61 200.08c-23.139-22.234-138.85-36.114-175.36-40.154a107.137 107.137 0 0 0 4.517-12.944 146.107 146.107 0 0 1 15.905-5.901c.677 1.997 3.865 9.648 5.682 13.279 73.415 2.025 77.184-54.557 80.17-70.058 2.92-15.157 2.771-29.802 27.953-56.575-37.516-10.933-91.467 16.945-109.54 58.437-6.79-2.545-13.597-4.424-20.328-5.586-4.824-19.46-29.944-73.672-95.863-73.672-83.46 0-174.43 68.853-174.43 185.41 0 97.976 66.891 183.84 104.68 183.84 16.505 0 30.703-12.36 34.036-23.44 2.795 7.597 11.368 31.213 14.184 37.225 4.162 8.89 23.41 16.583 31.833 7.357 10.83 6.017 30.703 9.641 41.534-6.405 20.86 4.412 39.3-8.026 39.702-22.868 10.235-.546 15.256-14.918 13.021-26.363-1.647-8.426-19.248-38.66-26.113-49.098 13.59 11.054 48.013 14.183 52.194.007 21.911 17.198 56.057 8.171 58.765-5.815 26.624 6.917 57.16-8.276 52.146-26.676 42.771-2.958 37.296-48.464 25.296-59.996z" fill="#543729" stroke-width=".973"/><path d="M328.514 103.025c9.212-18.277 20.788-38.234 35.409-50.58-16.093 6.485-31.981 25.873-41.375 46.595a144.914 144.914 0 0 0-14.552-8.132c13.105-27.972 43.555-51.332 77.112-53.157-22.477 20.385-14.498 62.754-32.979 85.183-5.288-5.311-17.43-15.562-23.615-19.909zm-14.53 29.762c.01-.7.272-6.094.763-8.557-1.288-.304-9.3-1.87-13.476-1.772-.304 5.245 2.204 14.17 4.684 19.541 17.075-.358 29.408-5.471 36.667-10.172-6.18-2.88-16.726-5.442-24.745-6.974-.894 1.851-3.097 6.568-3.892 7.934z" fill="#00acee"/><g stroke-width=".973"><path d="M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z" fill="#2baf2b"/><path d="M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z" fill="#ffcc2f"/><path d="M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a67.843 67.843 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z" fill="#cecece"/><path d="M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z" fill="#ef5734"/><path d="M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191c0-21.644-17.548-39.191-39.192-39.191-21.644 0-39.19 17.547-39.19 39.191z" fill="#ffcc2f"/><path d="M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464z" fill="#543729"/><ellipse cx="180.22" cy="98.044" rx="13.673" ry="8.501" fill="#fff"/></g></g></symbol><symbol viewBox="0 0 140 140" id="browserlist" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><path d="M70.314 10.066a59.828 59.828 0 0 0-59.828 59.828 59.828 59.828 0 0 0 59.828 59.828 59.828 59.828 0 0 0 59.828-59.828 59.828 59.828 0 0 0-59.828-59.828zm-4.836 8.785c.496 4.043 1.352 7.322 2.572 10.223 4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.128 5.128 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435v.002c-.487 1.055-2.002 2.343-3.497 3.219-4.075 2.39-11.172 5.736-20.914 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.083.197-7.485-.402-10.591-5.582 3.218-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.678-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.391 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zm26.465 19.557c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67.143 67.143 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.428-10.164-7.412-16.281z" fill="#ffca28" stroke-width=".855"/></symbol><symbol viewBox="0 0 140 140" id="browserlist_light" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><g transform="translate(10.823 10.1)" stroke-width=".855"><circle cx="59.492" cy="59.795" r="59.828" fill="#ffca28"/><path d="M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.128 5.128 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.275.867-4.613 2.704-10.256 5.019-16.871 6.506-1.071.24-2.154.458-3.25.649-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732z" fill="#37474f"/></g></symbol><symbol viewBox="0 0 24 24" id="bucklescript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm14.1 8.858a5.5 5.5 0 0 1 1.26.145c.417.093.778.213 1.082.357v1.723h-.18a3.281 3.281 0 0 0-.959-.603 2.867 2.867 0 0 0-1.155-.247c-.14 0-.277.011-.416.035a1.4 1.4 0 0 0-.395.12.756.756 0 0 0-.291.231.54.54 0 0 0-.123.348c0 .198.065.35.196.456.13.104.376.2.738.288.237.057.466.11.683.164.22.054.455.128.706.222.496.188.86.444 1.095.77.238.32.357.738.357 1.253 0 .737-.271 1.336-.813 1.798-.538.46-1.27.689-2.197.689a5.447 5.447 0 0 1-1.402-.161 6.725 6.725 0 0 1-1.117-.416v-1.794h.183c.344.318.73.563 1.155.734.429.17.839.256 1.233.256.1 0 .235-.01.4-.03.166-.02.3-.055.403-.102a.97.97 0 0 0 .313-.225c.084-.09.127-.223.127-.4a.568.568 0 0 0-.183-.424c-.119-.12-.294-.213-.526-.276-.243-.067-.5-.128-.773-.185a5.523 5.523 0 0 1-.76-.227c-.544-.204-.936-.48-1.177-.828-.237-.351-.357-.786-.357-1.305 0-.697.27-1.265.81-1.703.54-.442 1.235-.663 2.083-.663zm-8.981.135h2.51c.521 0 .903.02 1.143.06.243.041.484.13.721.266.246.144.43.338.548.583.121.24.181.518.181.83 0 .36-.082.68-.247.959a1.697 1.697 0 0 1-.7.642v.04c.423.098.758.298 1.004.603.249.305.373.706.373 1.205 0 .361-.063.686-.19.97-.125.285-.296.52-.516.707a2.31 2.31 0 0 1-.845.472c-.304.094-.69.141-1.159.141H8.12v-7.478zm1.659 1.372v1.582h.262c.263 0 .486-.007.672-.017.185-.01.332-.043.44-.1.15-.077.248-.175.294-.295.046-.124.07-.266.07-.427a.91.91 0 0 0-.083-.371.518.518 0 0 0-.282-.277 1.187 1.187 0 0 0-.456-.086c-.18-.007-.433-.01-.76-.01h-.157zm0 2.873V18.1H9.9c.469 0 .804-.002 1.007-.006.202-.003.39-.046.56-.13a.712.712 0 0 0 .357-.33c.067-.142.099-.302.099-.483 0-.237-.04-.42-.121-.547-.078-.13-.214-.228-.405-.291a1.842 1.842 0 0 0-.538-.072 49.47 49.47 0 0 0-.716-.003h-.366z" fill="#26a69a" stroke-width="1.067"/></symbol><symbol viewBox="0 0 24 24" id="c" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 15.97l.42 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96-1.14-1.27-1.68-2.88-1.68-4.83C6 9.9 6.68 8.13 8 6.89 9.28 5.64 10.92 5 12.9 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.6 2.49-1.04-.34c-.4-.1-.87-.15-1.4-.15-1.15-.01-2.11.36-2.86 1.1-.76.73-1.14 1.85-1.18 3.34.01 1.36.37 2.42 1.08 3.2.71.77 1.7 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.09-.32z" fill="#0277bd"/></symbol><symbol viewBox="0 0 300 300" id="cabal" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -822.52)" fill-rule="evenodd" color="#000"><rect transform="matrix(-.98339 .18149 .60192 .79856 0 0)" x="405.55" y="967.22" width="107.25" height="156.59" rx="12.306" ry="12.31" fill="#2d9bbd"/><rect transform="matrix(-.98528 .17093 -.59175 .80612 0 0)" x="-1156.5" y="1461.9" width="108.34" height="123.15" rx="10.69" ry="12.31" fill="#4a4bcd"/><path d="M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932c2.72-1.658 32.808 2.503 34.15-1.01 1.343-3.515-18.174-35.352-18.721-39.381-.548-4.03 9.732-40.12 7.502-43.125-2.229-3.005-30.06 9.427-33.118 8.594-3.059-.833-26.793-27.3-29.514-25.643-2.72 1.657-.405 41.177-1.747 44.693z" fill="#2e5bc1"/></g></symbol><symbol viewBox="0 0 24 24" id="cake" xmlns="http://www.w3.org/2000/svg"><path d="M12.254 6.621a1.807 1.807 0 0 0 1.808-1.807c0-.344-.09-.66-.262-.932l-1.546-2.684-1.546 2.684a1.72 1.72 0 0 0-.262.932 1.808 1.808 0 0 0 1.808 1.807m4.158 9.04l-.967-.976-.976.976c-1.175 1.166-3.236 1.175-4.42 0l-.959-.976-.994.976a3.134 3.134 0 0 1-3.977.353v4.167a.904.904 0 0 0 .904.904h14.463a.904.904 0 0 0 .904-.904v-4.167a3.134 3.134 0 0 1-3.977-.353m1.265-6.328h-4.52V7.525H11.35v1.808H6.83a2.712 2.712 0 0 0-2.711 2.712v1.392c0 .977.795 1.772 1.771 1.772.489 0 .94-.18 1.248-.515l1.952-1.926 1.908 1.926c.669.669 1.835.669 2.504 0l1.916-1.926 1.944 1.926c.316.334.768.515 1.247.515.976 0 1.78-.795 1.78-1.772v-1.392a2.712 2.712 0 0 0-2.711-2.712z" fill="#ff7043" stroke-width=".904"/></symbol><symbol viewBox="0 0 24 24" id="certificate" xmlns="http://www.w3.org/2000/svg"><path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3 3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2 3-2v3.5l3 1.5-3 1.5V15l-3-2-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2z" fill="#ff5722"/></symbol><symbol viewBox="0 0 24 24" id="changelog" xmlns="http://www.w3.org/2000/svg"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="clojure" xmlns="http://www.w3.org/2000/svg"><path d="M3.355 1.78c-.845 0-1.525.68-1.525 1.525v17.441c0 .845.68 1.525 1.525 1.525h17.442c.845 0 1.525-.68 1.525-1.525V3.305c0-.845-.68-1.526-1.525-1.526H3.355zm6.168 2.572h1.963l6.368 14.931H15.93l-3.38-8.086-3.349 8.086H7.21l4.346-10.38-2.032-4.551z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="cmake" xmlns="http://www.w3.org/2000/svg"><path d="M11.99 2.965L2.977 20.999l9.874-8.47-.863-9.564z" fill="#1e88e5"/><path d="M12.007 2.963l.002.29 1.312 14.498-.001.006.023.26 7.362 2.979h.416l-.158-.311-.114-.228h-.002l-8.84-17.494z" fill="#e53935"/><path d="M8.607 16.11L2.98 20.995h17.743v-.016L8.607 16.11z" fill="#7cb342"/></symbol><symbol class="bfmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate" xmlns="http://www.w3.org/2000/svg"><path class="bfsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#eee" stroke-width="2.849"/></symbol><symbol class="bgmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate_light" xmlns="http://www.w3.org/2000/svg"><path class="bgsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#455a64" stroke-width="2.849"/></symbol><symbol viewBox="0 0 24 24" id="coffee" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="coldfusion" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(90)" x="2.283" y="-21.86" width="19.487" height="19.487" ry="0" fill="#0d3858" stroke="#4dd0e1" stroke-width=".7"/><text x="6.653" y="16.426" fill="#4dd0e1" font-family="Calibri" font-size="29.001" font-weight="bold" letter-spacing="0" stroke-width=".725" word-spacing="0" style="line-height:1.25"><tspan x="6.653" y="16.426" font-family="'Segoe UI'" font-size="10.634" font-weight="normal">C<tspan font-size="11.844">f</tspan></tspan></text></symbol><symbol viewBox="0 0 24 24" id="conduct" xmlns="http://www.w3.org/2000/svg"><path d="M10 17l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#cddc39"/></symbol><symbol viewBox="0 0 24 24" id="console" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></symbol><symbol viewBox="0 0 24 24" id="contributing" xmlns="http://www.w3.org/2000/svg"><path d="M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="cpp" xmlns="http://www.w3.org/2000/svg"><path d="M10.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C1.56 15.77 1 14.16 1 12.21c.05-2.31.72-4.08 2-5.32C4.32 5.64 5.96 5 7.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M11 11h2V9h2v2h2v2h-2v2h-2v-2h-2v-2m7 0h2V9h2v2h2v2h-2v2h-2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="credits" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v2H3V3m4 4h10v2H7V7m-4 4h18v2H3v-2m4 4h10v2H7v-2m-4 4h18v2H3v-2z" fill="#9ccc65"/></symbol><symbol viewBox="0 0 200 200" id="crystal" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" stroke-width="1.153" fill="#cfd8dc"/></symbol><symbol viewBox="0 0 200 200" id="crystal_light" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" fill="#37474f" stroke-width="1.153"/></symbol><symbol viewBox="0 0 24 24" id="csharp" xmlns="http://www.w3.org/2000/svg"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M13.89 19l.61-4H13l.34-2h1.5l.32-2h-1.5L14 9h1.5l.61-4h2l-.61 4h1l.61-4h2l-.61 4H22l-.34 2h-1.5l-.32 2h1.5L21 15h-1.5l-.61 4h-2l.61-4h-1l-.61 4h-2m2.95-6h1l.32-2h-1l-.32 2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="css" xmlns="http://www.w3.org/2000/svg"><path d="M5 3l-.65 3.34h13.59L17.5 8.5H3.92l-.66 3.33h13.59l-.76 3.81-5.48 1.81-4.75-1.81.33-1.64H2.85l-.79 4 7.85 3 9.05-3 1.2-6.03.24-1.21L21.94 3H5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="css-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#42a5f5"/><path d="M4.676 3l-.488 2.51h10.211l-.33 1.623H3.864l-.496 2.502H13.58l-.57 2.863-4.119 1.36-3.569-1.36.248-1.232H3.06l-.593 3.005 5.898 2.254 6.8-2.254.902-4.53.18-.91L17.406 3H4.675z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 33 33" id="cucumber" xmlns="http://www.w3.org/2000/svg"><title>cucumber-mark-transparent-pips</title><g transform="translate(0 -5)" fill="none" fill-rule="evenodd"><path d="M-4-1h40v40H-4z"/><path d="M16.641 7.092c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64C21.824 34.77 28.561 28.73 29.063 20.8c.303-4.772-2.076-9.644-6.09-12.01a10.575 10.575 0 0 0-1.455-.728l-.243-.097c-.223-.082-.448-.175-.68-.242a12.614 12.614 0 0 0-3.954-.632zm2.62 4.707a1.387 1.387 0 0 0-1.213.485c-.233.31-.379.611-.534.923-.466 1.087-.31 2.251.388 3.105 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.404-1.359-1.479zm-5.654.073c-.708.068-1.382.63-1.382 1.407 0 .31.087.709.243 1.02.466 1.086 1.46 1.78 2.546 2.013.621-.854.782-2.018.316-3.105-.155-.311-.3-.617-.534-.85a1.364 1.364 0 0 0-1.188-.485zm-3.809 3.735c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.559 1.087.466 2.344.306 3.275-.316-.233-1.009-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.009-.161-.004-.243 0zm11.961 4.708a3.551 3.551 0 0 0-2.013.582c.233 1.01 1.023 1.936 2.11 2.401.389.156.705.244 1.093.244 1.397.077 2.08-1.65.994-2.427-.31-.233-.611-.379-.922-.534a3.354 3.354 0 0 0-1.262-.266zm-10.603.072a3.376 3.376 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402.388 0 .782-.087 1.092-.243 1.087-.465 1.859-1.392 2.014-2.401a3.474 3.474 0 0 0-1.99-.582zm3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.995-.077 1.32 1.724 2.028 2.5 1.02.233-.312.378-.613.534-.923.466-1.01.306-2.174-.316-3.106zm2.887.073c-.621.854-.781 2.019-.315 3.106.155.31.3.615.534.848.854.932 2.65.243 2.572-.921 0-.31-.088-.71-.243-1.02-.466-1.087-1.46-1.78-2.547-2.013z" fill="#4caf50" stroke-width=".776"/></g></symbol><symbol id="cuda" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style>.bust0{fill:#76b900}</style><title>NVIDIA-Logo</title><path id="buEye_Mark" class="bust0" d="M76.362 75.199V64.116c1.095-.068 2.19-.137 3.284-.137 30.377-.958 50.286 26.135 50.286 26.135s-21.483 29.83-44.539 29.83c-3.079 0-6.089-.48-8.962-1.438v-33.66c11.836 1.436 14.23 6.636 21.277 18.471l15.804-13.273s-11.562-15.12-30.992-15.12c-2.053-.068-4.105.069-6.158.274m0-36.67v16.556l3.284-.205c42.213-1.437 69.784 34.618 69.784 34.618s-31.608 38.45-64.516 38.45c-2.873 0-5.678-.274-8.483-.753v10.262c2.326.274 4.72.48 7.046.48 30.65 0 52.817-15.668 74.3-34.14 3.558 2.874 18.13 9.784 21.14 12.794-20.388 17.104-67.937 30.856-94.893 30.856-2.6 0-5.062-.137-7.525-.41v14.436h116.44V38.532zm0 79.977v8.757C48.038 122.2 40.17 92.712 40.17 92.712s13.615-15.05 36.192-17.514v9.579h-.068c-11.836-1.437-21.14 9.646-21.14 9.646s5.268 18.678 21.209 24.082M26.077 91.481S42.839 66.714 76.43 64.115v-9.03C39.213 58.094 7.057 89.565 7.057 89.565s18.199 52.68 69.305 57.47v-9.579c-37.492-4.652-50.286-45.975-50.286-45.975z" fill="#8bc34a" stroke-width=".684"/></symbol><symbol viewBox="0 0 24 24" id="dart" xmlns="http://www.w3.org/2000/svg"><title>Dart</title><path d="M12.486 1.385a.978.978 0 0 0-.682.281l-.01.007-6.387 3.692 6.371 6.372v.004l7.659 7.659 1.46-2.63-5.265-12.64-2.456-2.457a.972.972 0 0 0-.69-.288z" fill="#00ca94"/><path d="M5.422 5.35L1.73 11.733l-.007.01a.967.967 0 0 0 .006 1.371l3.059 3.061 11.963 4.706 2.704-1.502-.073-.073-.018.002-7.5-7.512h-.01L5.423 5.35z" fill="#1565c0"/><path d="M5.405 5.353l6.518 6.525h.01l7.502 7.51 2.855-.544.005-8.449-3.016-2.955c-.66-.647-1.675-1.064-2.695-1.202l.002-.032-11.181-.853z" fill="#1565c0"/><path d="M5.414 5.361l6.521 6.522v.009l7.506 7.506-.546 2.855h-8.448l-2.954-3.017c-.647-.66-1.064-1.676-1.2-2.696l-.033.003L5.414 5.36z" fill="#00ee94"/></symbol><symbol viewBox="0 0 24 24" id="database" xmlns="http://www.w3.org/2000/svg"><path d="M12 3C7.58 3 4 4.79 4 7s3.58 4 8 4 8-1.79 8-4-3.58-4-8-4M4 9v3c0 2.21 3.58 4 8 4s8-1.79 8-4V9c0 2.21-3.58 4-8 4s-8-1.79-8-4m0 5v3c0 2.21 3.58 4 8 4s8-1.79 8-4v-3c0 2.21-3.58 4-8 4s-8-1.79-8-4z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="diff" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h2v-2H3V3h11v2h2V3c0-1.11-.89-2-2-2H3m6 6c-1.11 0-2 .89-2 2v2h2V9h2V7H9m4 0v2h1v1h2V7h-3m5 0v2h2v11H9v-2H7v2c0 1.11.89 2 2 2h11c1.11 0 2-.89 2-2V9c0-1.11-.89-2-2-2h-2m-4 5v2h-2v2h2c1.11 0 2-.89 2-2v-2h-2m-7 1v3h3v-2H9v-1H7z" fill="#42a5f5"/></symbol><symbol id="docker" viewBox="0 0 41 34.5" xmlns="http://www.w3.org/2000/svg"><style id="bystyle2">.byst0{fill:#fff}.byst1{clip-path:url(#bySVGID_4_)}</style><g id="byg34" transform="translate(.292 1.9)" fill="#0087c9"><g id="byg32"><g id="byg30"><g id="byg28"><g id="byg26"><g id="byg9"><path id="bySVGID_1_" class="byst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g></g></g></g></g></g></symbol><symbol viewBox="0 0 24 24" id="document" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z" fill="#42a5f5"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone_light" xmlns="http://www.w3.org/2000/svg"><g fill="#424242" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol viewBox="0 0 3473 3473" id="editorconfig" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><defs id="ccdefs4"><style id="ccstyle2">.ccfil2{fill:#020202}.ccfil0{fill:#e3e3f8}.ccfil5{fill:#efefef}.ccfil6{fill:#faf1f1}.ccfil3{fill:#fdf2f2}.ccfil1{fill:#fdfdfd}.ccfil4{fill:#fef3f3}</style></defs><g id="ccLayer_x0020_1" transform="matrix(.8945 0 0 .8945 138.649 275.985)"><g id="cc_631799120"><g id="ccg11"><path class="ccfil0" d="M967 1895c46-30 84-105 61-158-63 27-60 89-61 158z" id="ccpath7" fill="#e3e3f8"/><path class="ccfil0" d="M1679 2067c50-16 98-72 71-130-39 27-64 64-71 130z" id="ccpath9" fill="#e3e3f8"/></g><g id="ccg21"><path class="ccfil1" d="M280 2895c0 63 16 131 60 155 162 91 730 20 923-23 101-23 183-98 278-139 214-93 369-168 540-293 124-91 321-347 342-500l-169-38c-4 172-43 211-196 251-103 28-304 34-409 16-139-23-202-96-265-179-122-162 27-275-166-286-203 249-561 70-718 45-67 97-224 727-222 871 97-33 158 3 245 37 308 119 39 224-84 193-84-20-110-75-159-110z" id="ccpath13" fill="#fdfdfd"/><path class="ccfil1" d="M683 1458c125 24 236 76 342 129 173 86 204 74 220 194 2 22-2 34 61 54 106 33-61-26 223-25 169 1 556 69 681 148 52 33 42 75 218 70-2-207-57-516-138-706-99-230-230-265-497-351-156-50-614-105-756-17-133 83-158 182-282 356-36 51-49 90-72 148z" id="ccpath15" fill="#fdfdfd"/><path class="ccfil1" d="M1784 1883c100 41-5 306-144 242-45-127 62-199 91-256-60-9-231-36-282-17-66 25-81 166-47 232 160 314 867 247 792 3-30-99-58-115-159-149-81-27-162-55-251-55z" id="ccpath17" fill="#fdfdfd"/><path class="ccfil1" d="M527 1848c80 77 261 89 378 95 15-155 28-271 152-262 61 83 29 181-35 244 109-1 172-83 156-202-92-66-371-198-511-217-39 42-135 272-140 342z" id="ccpath19" fill="#fdfdfd"/></g><path class="ccfil2" d="M339 2838c66-6 238 44 252 100-107 13-243 3-252-100zm-59 57c49 35 75 90 159 110 123 31 392-74 84-193-87-34-148-70-245-37-2-144 155-774 222-871 157 25 515 204 718-45 193 11 44 124 166 286 63 83 126 156 265 179 105 18 306 12 409-16 153-40 192-79 196-251l169 38c-21 153-218 409-342 500-171 125-326 200-540 293-95 41-177 116-278 139-193 43-761 114-923 23-44-24-60-92-60-155zm1399-828c7-66 32-103 71-130 27 58-21 114-71 130zm105-184c89 0 170 28 251 55 101 34 129 50 159 149 75 244-632 311-792-3-34-66-19-207 47-232 51-19 222 8 282 17-29 57-136 129-91 256 139 64 244-201 144-242zm-817 12c1-69-2-131 61-158 23 53-15 128-61 158zm-440-47c5-70 101-300 140-342 140 19 419 151 511 217 16 119-47 201-156 202 64-63 96-161 35-244-124-9-137 107-152 262-117-6-298-18-378-95zm-100-80c-37-102-37-261 120-274l-80 223c-21 48-21 37-40 51zm256-310c23-58 36-97 72-148 124-174 149-273 282-356 142-88 600-33 756 17 267 86 398 121 497 351 81 190 136 499 138 706-176 5-166-37-218-70-125-79-512-147-681-148-284-1-117 58-223 25-63-20-59-32-61-54-16-120-47-108-220-194-106-53-217-105-342-129zm1770-49c-19-63 16-59 77-102 35-25 63-51 106-75 161-90 461-105 589 2 52 43 137 127 124 237-27 219-177 339-300 439-125 102-333 207-548 137-18-44-4-323-25-426-19-92-9-102 44-157 156-162 494-280 686-141 81 60 58 83 100 129 52-56-45-244-403-232-243 8-348 198-450 189zM997 840c5-139 133-427 261-527 155-120 317-233 555-98 59 33 56 50 62 132 5 79-2 108-22 172-158 510-290 217-796 338 19-166 163-314 243-391 137-133 236-219 442-191 57 95 63 155-6 266-92 148-115 139-101 240 72-18 94-88 127-158 201-420-91-471-270-394-120 51-334 287-404 429-14 28-29 64-42 95zm792 21c21-125 145-156 145-541 0-166-204-315-471-204-229 94-264 166-386 350-115 174-111 365-210 526-29 46-55 62-87 108-23 34-40 77-63 117-47 77-95 133-133 225-120 3-221 5-233 129-16 170 64 212 64 276-1 69-281 765-203 1180 22 114 97 115 217 129 289 35 664 23 923-81l470-225c119-67 319-194 408-287 63-65 96-120 150-197 74-108 76-106 92-253 98 18 281 61 342 114-7 69-41 36-41 98 39 1 104-48 120-102-41-60-84-50-143-98 47-37 132-54 197-81 140-58 379-234 438-394 47-129 12-344-64-428-80-88-266-133-418-133-181 0-368 130-514 186-56-49-60-105-101-159-47-64-353-224-499-255z" id="ccpath23" fill="#020202"/><path class="ccfil3" d="M2453 1409c102 9 207-181 450-189 358-12 455 176 403 232-42-46-19-69-100-129-192-139-530-21-686 141-53 55-63 65-44 157 21 103 7 382 25 426 215 70 423-35 548-137 123-100 273-220 300-439 13-110-72-194-124-237-128-107-428-92-589-2-43 24-71 50-106 75-61 43-96 39-77 102z" id="ccpath25" fill="#fdf2f2"/><path class="ccfil4" d="M997 840l49-87c13-31 28-67 42-95 70-142 284-378 404-429 179-77 471-26 270 394-33 70-55 140-127 158-14-101 9-92 101-240 69-111 63-171 6-266-206-28-305 58-442 191-80 77-224 225-243 391 506-121 638 172 796-338 20-64 27-93 22-172-6-82-3-99-62-132-238-135-400-22-555 98-128 100-256 388-261 527z" id="ccpath27" fill="#fef3f3"/><path class="ccfil5" d="M427 1768c19-14 19-3 40-51l80-223c-157 13-157 172-120 274z" id="ccpath29" fill="#efefef"/><path class="ccfil6" d="M591 2938c-14-56-186-106-252-100 9 103 145 113 252 100z" id="ccpath31" fill="#faf1f1"/></g></g></symbol><symbol viewBox="0 0 24 24" id="elixir" xmlns="http://www.w3.org/2000/svg"><path d="M12.431 22.383c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.774-8.172 4.916-10.91 1.014-1.295 2.931-2.321 2.931-2.321s-.982 5.238 1.683 7.318c2.365 1.847 4.105 4.25 4.105 6.363 0 4.232-2.784 7.68-6.645 7.68z" fill="#9575cd" stroke-width="1.256"/></symbol><symbol viewBox="0 0 323.00001 322.99999" id="elm" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.8053 0 0 .8053 30.106 31.524)"><path fill="#f0ad00" d="M160.8 153.865l68.028-68.03H92.77z"/><path fill="#7fd13b" d="M160.983 5.098H12.033l68.524 68.525H229.51z"/><path fill="#7fd13b" stroke-width=".974" d="M243.906 88.021l74.136 74.137-74.474 74.475-74.137-74.137z"/><path fill="#60b5cc" d="M318.2 145.045V5.098H178.252z"/><path fill="#5a6378" d="M152.164 162.499L3.4 13.733v297.533z"/><path fill="#f0ad00" d="M252.205 245.27l65.995 65.996v-131.99z"/><path fill="#60b5cc" d="M160.8 171.134L12.034 319.899h297.53z"/></g></symbol><symbol viewBox="0 0 24 24" id="email" xmlns="http://www.w3.org/2000/svg"><path d="M20 8l-8 5-8-5V6l8 5 8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 30 30" id="erlang" xmlns="http://www.w3.org/2000/svg"><path style="line-height:1.25;-inkscape-font-specification:'Wide Latin'" d="M5.217 4.367c-.048.052-.097.1-.144.153C2.697 7.182 1.51 10.798 1.51 15.366c0 4.418 1.156 7.862 3.46 10.34h19.414c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52-1.454 1.381c-.866.773-.845.931-2.314 1.78-1.496.674-3.04.966-4.634.966-2.516 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.097-6.68l17.458.067-.182-1.472s-.847-7.129-2.542-9.372zm8.76.846c1.565 0 3.22.535 3.96 1.471.742.937.932 1.667.974 3.524H9.12c.111-1.955.436-2.81 1.372-3.697.937-.888 2.03-1.298 3.484-1.298z" font-weight="400" font-size="48" font-family="Wide Latin" letter-spacing="0" word-spacing="0" fill="#f44336" stroke-width=".97"/></symbol><symbol viewBox="0 0 299.99999 300.00001" id="eslint" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2.88 18.438) scale(1.0344)"><path d="M97.021 99.016l48.432-27.962c1.212-.7 2.706-.7 3.918 0l48.433 27.962a3.92 3.92 0 0 1 1.959 3.393v55.924a3.924 3.924 0 0 1-1.959 3.394l-48.433 27.962c-1.212.7-2.706.7-3.918 0l-48.432-27.962a3.92 3.92 0 0 1-1.959-3.394v-55.924a3.922 3.922 0 0 1 1.959-3.393" fill="#7986cb"/><path d="M273.34 124.49L215.473 23.82c-2.102-3.64-5.985-6.325-10.188-6.325H89.545c-4.204 0-8.088 2.685-10.19 6.325L21.488 124.27c-2.102 3.641-2.102 8.236 0 11.877l57.867 99.847c2.102 3.64 5.986 5.501 10.19 5.501h115.74c4.203 0 8.087-1.805 10.188-5.446l57.867-100.01c2.104-3.639 2.104-7.907.001-11.547m-47.917 48.41c0 1.48-.891 2.849-2.174 3.59l-73.71 42.527a4.194 4.194 0 0 1-4.17 0l-73.767-42.527c-1.282-.741-2.179-2.109-2.179-3.59V87.847c0-1.481.884-2.849 2.167-3.59l73.707-42.527a4.185 4.185 0 0 1 4.168 0l73.772 42.527c1.283.741 2.186 2.109 2.186 3.59z" fill="#3f51b5"/></g></symbol><symbol viewBox="0 0 24 24" id="exe" xmlns="http://www.w3.org/2000/svg"><path d="M19 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14m0 14V8H5v10h14z" fill="#e64a19"/></symbol><symbol viewBox="0 0 24 24" id="favicon" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="file" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 400 400" id="firebase" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 103)"><path d="M72.55 208.77l44.456-292.29 56.209 90.445L195.49-37.57 330.6 209.28z" fill="#ffa712"/><path d="M195.7 276.73l134.9-67.45-46.5-224.83L72.55 208.77z" fill="#fcca3f"/><path d="M173.22 6.932L72.56 208.772l136.35-144.58z" fill="#f6820c"/></g></symbol><symbol viewBox="0 0 24 24" id="flash" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cma"><stop offset="0" stop-color="#d92f3c"/><stop offset="1" stop-color="#791223"/></linearGradient><linearGradient xlink:href="#cma" id="cmb" x1="2.373" y1="12.027" x2="21.86" y2="12.027" gradientUnits="userSpaceOnUse" gradientTransform="translate(-.09 -24.144)"/></defs><rect width="19.487" height="19.487" x="2.283" y="-21.86" transform="rotate(90)" ry="0" fill="url(#cmb)"/><path style="line-height:125%" d="M16.802 5.768l-.013.002a6.43 6.43 0 0 0-1.182.192 5.062 5.062 0 0 0-1.494.718c-.428.323-.817.72-1.17 1.191-.34.48-.682 1.032-1.022 1.66-.12.228-.233.424-.35.636v.002h-.004l-1.34 2.394-.005-.002c-.238.443-.461.847-.665 1.198a4.358 4.358 0 0 1-.716.94 2.79 2.79 0 0 1-.907.594c-.072.027-.161.042-.242.063h-.989v2.414h.989v-.002a6.427 6.427 0 0 0 1.185-.192 5.062 5.062 0 0 0 1.494-.718 5.94 5.94 0 0 0 1.171-1.191c.34-.48.681-1.033 1.021-1.66.12-.228.235-.425.353-.637l.006.002.003-.005.037-.066h2.53v.002h1.124v-2.408h-.33v-.001h-1.98c.22-.407.432-.789.621-1.115.214-.37.452-.682.717-.94a2.79 2.79 0 0 1 .906-.594c.07-.027.16-.041.239-.061h.992V8.18h-.002V5.77h-.977v-.002z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol class="cnflow-logo" viewBox="0 0 299.99999 300" id="flow" xmlns="http://www.w3.org/2000/svg"><title>Flow logo</title><path d="M38.75 33.427l77.461 77.47H54.436l61.145 61.16H38.437l93.462 93.478v-77.158l.01-.01v-77.47h-.01V66.982h46.691l20.394 20.393H153.57v76.531h22.05l24.474 24.473h-15.806l-.01-.01v.01h-31.665l-.01-.01v.01h-.313l.313.313v77.148h109.149l-39.2-39.2v-15.806l8.465 8.466v-77.37h-15.682l.017-38.191 30.09 30.086V56.362h-64.874l-22.94-22.934H113.71z" fill="#fbc02d" fill-opacity=".976" stroke-width=".955" class="cnflow-logo-mark"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="coa" x1="-388.15%" x2="237.68%" y1="-144.18%" y2="430.41%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cob" x1="72.945%" x2="-97.052%" y1="84.424%" y2="-147.7%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="coc" x1="-283.88%" x2="287.54%" y1="-693.6%" y2="101.71%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cod" x1="-821.19%" x2="101.99%" y1="-469.05%" y2="288.24%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coe" x1="-140.36%" x2="419.01%" y1="-230.93%" y2="261.98%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cof" x1="191.08%" x2="20.358%" y1="253.95%" y2="20.403%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cog" x1="-388.09%" x2="237.67%" y1="-173.85%" y2="518.99%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#coa"/><linearGradient id="coj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cob"/><linearGradient id="cok" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#coc"/><linearGradient id="col" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cod"/><linearGradient id="com" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#coe"/><linearGradient id="con" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cof"/><linearGradient id="coo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cog"/><linearGradient id="cop" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#coh"/><linearGradient id="coh" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#coi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#coj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cok)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#col)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#com)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#con)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#coo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cop)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia-open" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cpi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpa"/><linearGradient id="cpa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cpb"/><linearGradient id="cpb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#cpc"/><linearGradient id="cpc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cpd"/><linearGradient id="cpd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#cpe"/><linearGradient id="cpe" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cpf"/><linearGradient id="cpf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpg"/><linearGradient id="cpg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpp" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#cph"/><linearGradient id="cph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#cpi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#cpj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cpk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#cpl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#cpm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#cpn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#cpo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cpp)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#cddc39" fill-rule="nonzero"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#cddc39"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00acc1" fill-rule="nonzero"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00acc1"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#e57373" fill-rule="nonzero"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" stroke-width=".644"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#e57373"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" fill-rule="evenodd" stroke-width=".644"/></symbol><symbol id="folder-docker" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs id="cydefs10"><path id="cySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><path id="cypath2" d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><style id="cystyle2">.cyst0{fill:#fff}.cyst1{clip-path:url(#cySVGID_4_)}</style><g id="cyg34" transform="translate(8.319 9.626) scale(.39491)" fill="#b3e5fc"><g id="cyg32"><g id="cyg30"><title id="cytitle4">Group 3</title><g id="cyg28"><g id="cyg26"><g id="cyg9"><path id="cySVGID_1_" class="cyst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="cyg24"><clipPath id="cySVGID_4_"><use id="cyuse14" width="100%" height="100%" xlink:href="#cySVGID_2_"/></clipPath><g id="cyg22" class="cyst1" clip-path="url(#cySVGID_4_)"><g id="cyg20"><g id="cyg18"><path id="cySVGID_3_" class="cyst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docker-open" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="cza"><use width="100%" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#SVGID_2_"/></clipPath></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><g transform="matrix(.3949 0 0 .39489 8.319 9.626)" fill="#b3e5fc"><title>Group 3</title><path class="czst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/><g class="czst1" clip-path="url(#cza)"><path class="czst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#01579b" fill-rule="nonzero"/><style>.dcst0{fill:#1173b6}.st1{fill:#585d67}</style><path class="dcst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#01579b"/><path class="ddst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M21.132 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.217 1.217m.608-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M21.133 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.216 1.217m.609-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ffca28" fill-rule="nonzero"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.411 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill-rule="nonzero" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ffca28"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.412 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#c0ca33" fill-rule="nonzero"/><path d="M17.39 12.544a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#c0ca33"/><path d="M17.391 12.543a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.036 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.968 4.968 0 0 1-2.679 2.203m-.155-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.1-1.238h2.894c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399h-1.806a4.902 4.902 0 0 1 2.672-2.202c-.37.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.049.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.94 4.94 0 0 1 2.679 2.202m-4.281-3.712a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.037 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.967 4.967 0 0 1-2.68 2.203m-.154-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.099-1.238h2.895c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399H13.06a4.902 4.902 0 0 1 2.672-2.202c-.371.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.05.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.941 4.941 0 0 1 2.679 2.202M17.34 9.322a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M16.473 13.927c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a17.015 17.015 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359a5.558 5.558 0 0 0-.203.604c.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.569-1.053c-.21-.372-.435-.702-.639-1.032-.38-.022-.78-.022-1.2-.022-.422 0-.823 0-1.202.022-.204.33-.428.66-.639 1.032l-.569 1.053.57 1.054c.21.372.434.702.638 1.032.38.021.78.021 1.201.021.421 0 .822 0 1.201-.02.204-.331.428-.661.639-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.245-9.284c-.436-.267-1.405.14-2.522 1.194.366.414.724.864 1.06 1.334.577.057 1.146.14 1.686.253.359-1.503.225-2.535-.224-2.78m-.492 4.03l.204.358c.077-.203.154-.407.203-.604-.19-.042-.4-.077-.618-.112l.211.358m1.018-4.95c1.033.589 1.145 2.141.71 3.953 1.784.527 3.069 1.398 3.069 2.584 0 1.187-1.285 2.058-3.07 2.585.436 1.812.324 3.364-.709 3.954-1.025.59-2.423-.085-3.77-1.37-1.35 1.285-2.747 1.96-3.78 1.37-1.025-.59-1.137-2.142-.702-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.203 6.538c.239.527.45 1.054.625 1.588 1.475-.443 2.303-1.075 2.303-1.588 0-.512-.828-1.144-2.303-1.587a15.81 15.81 0 0 1-.625 1.587m-7.136 0a15.806 15.806 0 0 1-.625-1.587c-1.474.443-2.303 1.075-2.303 1.587 0 .513.829 1.145 2.303 1.588.176-.534.387-1.06.625-1.588m6.321 1.588l-.21.358c.217-.035.428-.07.617-.113-.049-.196-.126-.4-.203-.604l-.204.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.522 1.194.45-.246.583-1.278.224-2.781-.54.112-1.11.196-1.685.253-.337.47-.695.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.113.049.196.126.4.203.604l.204-.359m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M16.473 13.928c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a16.997 16.997 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359c-.077.204-.154.408-.203.604.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.57-1.053c-.21-.372-.434-.702-.638-1.032-.38-.022-.78-.022-1.201-.022-.421 0-.822 0-1.2.022-.205.33-.43.66-.64 1.032l-.569 1.053.569 1.054c.21.372.435.702.64 1.032.378.021.779.021 1.2.021.421 0 .822 0 1.2-.02.205-.33.43-.661.64-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.244-9.284c-.435-.267-1.404.14-2.52 1.194.364.414.723.864 1.06 1.334.575.057 1.144.14 1.685.253.358-1.503.225-2.535-.225-2.78m-.491 4.03l.203.358c.078-.203.155-.407.204-.604-.19-.042-.4-.077-.618-.112l.21.358m1.02-4.95c1.032.589 1.144 2.141.708 3.953 1.784.527 3.07 1.398 3.07 2.584 0 1.187-1.286 2.058-3.07 2.585.436 1.812.323 3.364-.709 3.954-1.025.59-2.423-.085-3.771-1.37-1.348 1.285-2.746 1.96-3.778 1.37-1.026-.59-1.138-2.142-.703-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.204 6.538c.24.527.45 1.054.625 1.588 1.475-.443 2.304-1.075 2.304-1.588 0-.512-.829-1.144-2.304-1.587a15.81 15.81 0 0 1-.625 1.587m-7.135 0a15.808 15.808 0 0 1-.625-1.587c-1.475.443-2.303 1.075-2.303 1.587 0 .513.828 1.145 2.303 1.588.176-.534.386-1.06.625-1.588m6.32 1.588l-.21.358c.218-.035.428-.07.618-.113a5.56 5.56 0 0 0-.204-.604l-.203.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.521 1.194.45-.246.583-1.278.225-2.781-.54.112-1.11.196-1.685.253-.338.47-.696.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.112.049.197.126.4.203.604l.204-.358m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#fbc02d" fill-rule="nonzero"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.434h-1.217v8.518a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#fbc02d"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.433h-1.217v8.519a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#4caf50" fill-rule="nonzero"/><g fill="#c8e6c9" transform="translate(2.065 -.225) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#4caf50"/><g fill="#c8e6c9" fill-rule="evenodd" transform="translate(2.064 -.224) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1e88e5" fill-rule="nonzero"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1e88e5"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.811 8.52l-5.988 5.506-3.346-2.522-1.383.805 3.298 3.03-3.298 3.032 1.383.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.622v6.396l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.81 8.52l-5.988 5.506-3.346-2.522-1.384.805 3.3 3.03-3.3 3.032 1.384.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.621v6.397l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><g transform="translate(8.459 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688"/><g transform="translate(8.458 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#03a9f4" fill-rule="nonzero"/><g transform="translate(9.192 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#03a9f4"/><g transform="translate(9.193 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol viewBox="0 0 24 24" id="font" xmlns="http://www.w3.org/2000/svg"><path d="M9.62 12L12 5.67 14.37 12M11 3L5.5 17h2.25l1.12-3h6.25l1.13 3h2.25L13 3h-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 500 500" id="fsharp" xmlns="http://www.w3.org/2000/svg"><path d="M235.906 36.66L21.963 250.601l213.943 213.943v-84.36L106.209 250.487l129.697-129.696z" fill="#378bba" stroke-width="14.706"/><path d="M235.906 156.614l-93.622 93.62 93.622 93.622z" fill="#378bba" stroke-width="15.006"/><path d="M263.417 36.64L477.36 250.583 263.417 464.526v-84.36l129.696-129.697-129.696-129.696z" fill="#30b9db" stroke-width="14.706"/></symbol><symbol viewBox="0 0 152.99 160.01" id="fusebox" xmlns="http://www.w3.org/2000/svg"><defs id="fkdefs4"><style id="fkstyle2">.fkcls-1{fill:#fff}.fkcls-2{fill:#515151}.fkcls-3{fill:#1d79bf}.fkcls-4{fill:#383838}</style></defs><title id="fktitle6">Asset 3</title><g id="fkLayer_2" data-name="Layer 2" transform="matrix(.87285 0 0 .87285 10.17 10.175)"><g id="fkFuse_Box" data-name="Fuse Box"><g id="fkLOGO"><path class="fkcls-1" id="fkpolygon8" fill="#fff" d="M76.56 2.19l74.22 24.93-7.7 87.77-65.41 42.66-69.79-43.93-5.7-86.13z"/><path class="fkcls-2" d="M77.69 160L5.87 114.81 0 26 76.55 0 153 25.67l-7.94 90.4zM9.88 112.43l67.77 42.66 63.45-41.39 7.47-85.13-72-24.18L4.36 28.95z" id="fkpath10" fill="#515151"/><path class="fkcls-3" id="fkpolygon12" fill="#1d79bf" d="M76.4 148.8V61.68l66.93-29.82-5.99 78.77z"/><path id="fkF" class="fkcls-4" fill="#383838" d="M76.4 148.8l-60.35-37.39L9.63 31.8 76.4 61.68z"/><path class="fkcls-1" d="M25.58 52.73l.54 15.93 37.35 18.18.12 14.69-37-18.21 1.64 37.1-14.56-9-5.05-80.55 67.79 30.82v15.46z" id="fkpath15" fill="#fff"/><path class="fkcls-1" d="M135.91 90.77c-.08 13.12-6.33 26.59-16.77 33.12l-42.8 27.93V61.71l42.27-18.84c5.16-2.41 9.51-1.43 12.4 3.11 1.9 3 2.89 7.23 2.86 12.21A35.69 35.69 0 0 1 129.34 76c4.29 2 6.66 6.55 6.57 14.77zM123 63.76c0-4.64-2-6.93-4.92-5.45l-29 14.48L89 90l29.44-15.59c2.5-1.32 4.56-5.91 4.56-10.65zM125.15 96c0-5.71-2.42-8.24-6.55-5.93L89 106.64v19.58l29.34-17.46c4.43-2.64 6.79-7.27 6.81-12.76z" id="fkpath17" fill="#fff"/><path id="fkTOP" class="fkcls-4" fill="#383838" d="M76.4 8.82L9.71 31.77l109.77 2.38-84.02 9.21L76.4 61.68l20.76-9.25-27.73-1.37 49.78-8.46 24.12-10.74z"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="git" xmlns="http://www.w3.org/2000/svg"><path d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2 2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82z" fill="#e64a19"/></symbol><symbol viewBox="0 0 164 158" id="gitlab" xmlns="http://www.w3.org/2000/svg"><path d="M161.26 62.6545L161.031 62.0689L138.845 4.16909C138.394 3.03428 137.595 2.07162 136.562 1.41923C135.529 0.777924 134.324 0.46905 133.11 0.534313C131.896 0.599576 130.731 1.03583 129.773 1.78418C128.825 2.55402 128.137 3.59717 127.804 4.77168L112.824 50.6027H52.1654L37.1855 4.77168C36.8606 3.59077 36.1716 2.54239 35.2164 1.77569C34.2581 1.02734 33.0931 0.591087 31.8789 0.525824C30.6647 0.460561 29.4597 0.769435 28.4267 1.41074C27.3965 2.06577 26.598 3.02759 26.1436 4.1606L3.91558 62.035L3.69492 62.6206C0.501198 70.9653 0.106986 80.1221 2.57171 88.7104C5.03644 97.2986 10.2265 104.853 17.3593 110.234L17.4357 110.293L17.6394 110.438L51.4355 135.747L68.1553 148.401L78.34 156.09C79.5313 156.995 80.986 157.485 82.4818 157.485C83.9775 157.485 85.4322 156.995 86.6235 156.09L96.8082 148.401L113.528 135.747L147.528 110.285L147.613 110.217C154.73 104.835 159.907 97.2884 162.368 88.7117C164.829 80.135 164.44 70.9913 161.26 62.6545V62.6545Z" fill="#e24329"/><path d="M161.26 62.6545L161.031 62.0688C150.221 64.2878 140.034 68.8667 131.198 75.4787L82.4733 112.322C99.0658 124.874 113.511 135.78 113.511 135.78L147.511 110.319L147.596 110.251C154.723 104.869 159.909 97.3176 162.373 88.7336C164.837 80.1496 164.447 70.9973 161.26 62.6545Z" fill="#fc6d26"/><path d="M51.4355 135.78L68.1553 148.435L78.34 156.124C79.5313 157.029 80.9859 157.518 82.4817 157.518C83.9775 157.518 85.4322 157.029 86.6235 156.124L96.8081 148.435L113.528 135.78C113.528 135.78 99.0657 124.84 82.4732 112.322C65.8807 124.84 51.4355 135.78 51.4355 135.78Z" fill="#fca326"/><path d="M33.7397 75.4787C24.9112 68.8531 14.7266 64.2622 3.91558 62.0349L3.69492 62.6205C0.501198 70.9652 0.106986 80.122 2.57171 88.7103C5.03644 97.2985 10.2265 104.853 17.3593 110.234L17.4357 110.293L17.6394 110.437L51.4355 135.746C51.4355 135.746 65.8638 124.84 82.4733 112.288L33.7397 75.4787Z" fill="#fc6d26"/></symbol><symbol viewBox="0 0 24 24" id="go" xmlns="http://www.w3.org/2000/svg"><path d="M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.123 5.502 4.757 5.502h2.286c2.634 0 4.757-2.453 4.757-5.502v-4.6a5.1 5.1 0 0 0 .026.003v-4.71c0-3.049-2.122-5.502-4.756-5.502h-2.287z" fill="#73cddc"/><rect width="2.289" height="3.335" x="-1.178" y="6.092" ry="1.125" transform="matrix(.4849 -.87457 .85979 .51065 0 0)" fill="#73cddc"/><rect width="2.297" height="3.39" x="10.261" y="-15.076" ry="1.143" transform="matrix(.44646 .8948 -.89204 .45195 0 0)" fill="#73cddc"/><circle cx="9.267" cy="5.13" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><circle cx="14.214" cy="5.116" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><ellipse cx="8.039" cy="5.051" rx=".792" ry=".901" fill="#030d18"/><path d="M11.792 9.556l.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.402.69.403.689 0 0 1-.404-.69.403.689 0 0 1 .035-.276z" fill="#fff" stroke="#fff" stroke-width=".155"/><ellipse cx="8.51" cy="5.365" rx=".138" ry=".166" fill="#fff"/><ellipse cx="12.945" cy="5.189" rx=".792" ry=".901" fill="#030d18"/><ellipse cx="13.414" cy="5.446" rx=".138" ry=".166" fill="#fff"/><ellipse cx="-12.982" cy="-3.409" rx=".708" ry="1.026" transform="rotate(-129.403)" fill="#f6d2a1" stroke-width=".4"/><path d="M11.772 9.553l-.757.135a.4.672 0 0 0-.008.135.4.672 0 0 0 .4.672.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z" fill="#fff" stroke="#fff" stroke-width=".153"/><ellipse cx="1.841" cy="-21.563" rx=".707" ry="1.026" transform="scale(1 -1) rotate(50.597)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="-17.281" cy="-21.784" rx=".864" ry="1.27" transform="matrix(.3054 -.95222 -.97065 -.2405 0 0)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="22.885" cy="2.587" rx=".864" ry="1.27" transform="matrix(.22652 .974 .95652 -.29167 0 0)" fill="#f6d2a1" stroke-width=".4"/><path d="M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.264.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .594-.267.594-.598V8.99a.595.595 0 0 0-.594-.597h-2.115z" fill="#f6d2a1" stroke="#657075" stroke-width=".1"/><ellipse cx="11.734" cy="8.203" rx="1.208" ry=".68" fill="#030d18" stroke="#fff" stroke-width=".162"/></symbol><symbol viewBox="0 0 24 24" id="gradle" xmlns="http://www.w3.org/2000/svg"><path d="M21.718 5.503c-.731-1.315-2.04-1.708-2.963-1.727-1.133-.023-2.065.605-1.888 1.017.037.088.25.55.38.741.19.275.527.064.646 0 .353-.187.73-.248 1.16-.198.409.048.954.3 1.319 1.001.859 1.652-1.794 5.05-5.114 2.697-3.32-2.353-6.548-1.574-8.01-1.1-1.462.475-2.135.952-1.556 2.055.785 1.498.524 1.038 1.285 2.28 1.21 1.97 3.856-.908 3.856-.908-1.972 2.906-3.662 2.204-4.31 1.188a15.864 15.864 0 0 1-1.038-1.97c-4.993 1.76-3.642 9.534-3.642 9.534h2.48c.632-2.862 2.892-2.757 3.28 0h1.892c1.673-5.59 5.914 0 5.914 0h2.466c-.69-3.812 1.388-5.01 2.697-7.246 1.31-2.235 2.551-4.969 1.146-7.364zm-6.362 7.362c-1.304-.426-.837-1.723-.837-1.723s1.139.368 2.68.87c-.09.403-.856 1.175-1.843.853z" fill="#0097a7" stroke-width=".47"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 300 300" id="graphcool" xmlns="http://www.w3.org/2000/svg"><path d="M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768 9.335-3.478 14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.313 17.313 0 0 1-8.635 14.95l-75.739 43.755a17.312 17.312 0 0 1-17.261 0l-75.74-43.756a17.312 17.312 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794c-3.397-9.327-12.962-14.904-22.751-13.27-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.932 31.932 0 0 0-15.978 27.588v87.454a31.933 31.933 0 0 0 15.927 27.602l75.74 43.755a31.934 31.934 0 0 0 31.846 0l75.74-43.755a31.933 31.933 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z" fill="#27ae60" stroke="#27ae60" stroke-width="7.883622079999999"/></symbol><symbol viewBox="0 0 400 400" id="graphql" xmlns="http://www.w3.org/2000/svg"><path d="M67.008 293.022l-13.143-7.588L200.282 31.839l13.143 7.588z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M50.855 265.174H343.69v15.177H50.855z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M203.122 358.269L56.649 273.7l7.589-13.143 146.472 84.568zm127.24-220.407L183.889 53.293l7.589-13.143 146.472 84.568z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M64.278 137.803l-7.588-13.142 146.472-84.568 7.588 13.143z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M327.661 293.025L181.244 39.43l13.143-7.589 146.417 253.596zM62.466 114.597h15.176v169.136H62.466zm254.528 0h15.176v169.136h-15.176z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M200.538 351.845l-6.628-11.481L321.3 266.812l6.629 11.48z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M352.284 288.67c-8.777 15.268-28.342 20.48-43.61 11.703-15.268-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.36 8.869 20.57 28.342 11.703 43.61M97.574 141.567c-8.778 15.268-28.343 20.48-43.61 11.703-15.269-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.268 8.869 20.479 28.342 11.702 43.61M42.353 288.67c-8.777-15.268-3.566-34.741 11.702-43.61 15.268-8.776 34.741-3.565 43.61 11.703 8.776 15.268 3.565 34.741-11.703 43.61-15.36 8.776-34.833 3.565-43.61-11.703m254.71-147.103c-8.776-15.268-3.565-34.741 11.703-43.61 15.268-8.776 34.742-3.565 43.61 11.703 8.777 15.268 3.566 34.741-11.702 43.61-15.268 8.776-34.833 3.565-43.61-11.703m-99.745 236.608c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907c0 17.554-14.262 31.907-31.907 31.907m0-294.206c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907-14.262 31.907-31.907 31.907" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/></symbol><symbol viewBox="0 0 24 24" id="groovy" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.982a10.119 10.119 0 0 0-10.12 10.12A10.119 10.119 0 0 0 12 22.22 10.119 10.119 0 0 0 22.12 12.1 10.119 10.119 0 0 0 12 1.983zm1.254 2.422c.91 0 1.647.261 2.213.78.571.518.857 1.188.857 2.013 0 .889-.319 1.673-.959 2.35-.64.677-1.376 1.015-2.207 1.015-.486 0-.89-.119-1.213-.357-.317-.238-.476-.532-.476-.88 0-.212.06-.4.181-.563.127-.164.274-.246.438-.246.159 0 .238.092.238.277 0 .164.06.29.182.38.121.09.261.136.42.136.423 0 .828-.29 1.215-.866.391-.582.587-1.202.587-1.863 0-.465-.151-.844-.453-1.135-.301-.296-.69-.445-1.166-.445-.714 0-1.406.318-2.078.953-.666.635-1.211 1.47-1.635 2.506-.417 1.031-.627 2.014-.627 2.945 0 .857.185 1.54.555 2.047.37.503.863.754 1.477.754 1.037 0 2.027-.734 2.974-2.2l1.493-.212c.185-.026.277.018.277.135 0 .053-.072.28-.215.681-.143.402-.337 1.074-.586 2.016.82-.476 1.455-1.003 1.904-1.58v.914c-.36.418-1.046.888-2.062 1.412-.212 1.407-.682 2.493-1.406 3.26-.725.772-1.54 1.16-2.444 1.16-.433 0-.775-.102-1.023-.303-.243-.2-.365-.477-.365-.832 0-.984.955-1.94 2.865-2.865.2-.714.395-1.356.586-1.928-.333.482-.817.907-1.451 1.278-.635.37-1.225.554-1.77.554-.889 0-1.628-.383-2.22-1.15-.588-.772-.881-1.748-.881-2.928 0-1.243.333-2.42 1-3.531a7.747 7.747 0 0 1 2.625-2.674c1.084-.672 2.134-1.008 3.15-1.008zM12.03 16.592c-1.375.687-2.062 1.365-2.062 2.031 0 .354.169.533.508.533.666 0 1.184-.856 1.554-2.564z" fill="#26c6da"/></symbol><symbol viewBox="0 0 24 24" id="gulp" xmlns="http://www.w3.org/2000/svg"><path d="M8.37 15.94a596.238 596.238 0 0 1-.482-4.982c.002-.042-.225-.077-.505-.077h-.508V8.95h3.966V5.198l1.871-1.124c1.14-.685 1.978-1.125 2.144-1.125.4 0 .866.506.866.939 0 .19-.057.422-.127.517-.07.095-.722.53-1.45.966l-1.321.792-.029 1.393-.028 1.393h3.972v1.932h-.98l-.495 4.983-.495 4.983H8.854l-.485-4.906z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="h" xmlns="http://www.w3.org/2000/svg"><path d="M16.745 19.818h-3.007v-5.882q0-2.381-1.736-2.381-.869 0-1.438.663-.56.662-.56 1.718v5.882H6.988V4.533h3.016v6.508h.037q1.186-1.802 3.193-1.802 3.511 0 3.511 4.239z" stroke-width=".478" fill="#0277bd"/></symbol><symbol viewBox="0 0 253.6 253.6" id="hack" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-29.243 -29.515) scale(1.2301)"><path fill="#607d8b" d="M69.496 159.551v52.576l51.77-52.576zM123.507 41.523l-54.01 52.755v55.084l54.01-54.009z"/><path fill="#eceff1" d="M130.023 95.663v51.501l52.128-51.5z"/><path fill="#607d8b" d="M185.465 101.867l-55.442 55.174v55.083l55.442-55.262z"/><path fill="#ffa000" d="M73.068 154.283l50.427.09v-50.248z"/></g></symbol><symbol viewBox="0 0 300 300.00001" id="haml" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 165.6)"><path d="M78.42-132.307c-12.047-.302-26.924 5.998-26.924 5.998l49.195 99.791L74.605 85.005c23.81 20.134 50.07 10.504 50.07 10.504L136.76 9.212c1.526 1.446 3.146 2.77 4.777 3.995 5.244 3.714 10.925 6.553 16.606 8.738 5.68 2.185 11.583 3.933 17.482 5.244 3.933.874 7.645 1.53 11.578 1.967-1.748 3.933-2.84 8.083-2.621 12.672 0 .437.22.873.656 1.092h.217c4.152 2.185 8.521 3.934 13.328 5.027 4.589.874 9.615 1.312 14.422.656 5.026-.655 10.051-2.623 13.984-5.9 3.933-3.278 6.774-7.648 8.522-12.237l.219-.218v-.217l.656-5.899v-.22c2.185-1.311 4.37-2.621 6.555-4.37 2.622-2.184 5.025-4.589 6.773-7.648 1.748-3.059 2.84-6.774 2.621-10.488-.218-3.496-1.53-6.99-3.06-10.049-1.53-3.059-3.495-5.901-5.68-8.523-4.37-5.026-9.614-9.176-15.295-12.454-5.462-3.496-11.581-6.338-17.7-8.304l-2.404-.656-1.962-.655c-1.311-.437-2.406-1.092-3.498-1.53-2.185-1.31-3.717-2.622-4.809-4.37-2.185-3.278-2.403-8.301-1.31-13.545.218-1.311.656-2.623 1.093-3.934a96.064 96.064 0 0 0 1.31-4.152c.314-1.412.51-2.829.598-4.402l29.203-25.553c-2.275-8.404-27.488-17.158-27.488-17.158l-74.931 63.726-43.243-81.584c-1.553-.35-3.218-.527-4.94-.57zm107.682 73.14c-.449 2.336-.647 4.795-.647 7.258.219 3.715 1.311 7.87 3.715 11.366 2.403 3.496 5.68 6.117 8.957 7.646a29.663 29.663 0 0 0 5.027 1.967l2.623.654 2.184.438c5.68 1.53 11.142 3.714 16.168 6.554 5.025 2.84 9.833 6.337 13.766 10.27s6.992 8.959 7.43 13.984c.218 3.496-.22 6.118-1.313 8.303-1.093 2.404-2.84 4.588-4.807 6.555-.874.874-1.966 1.747-2.84 2.402a27.11 27.11 0 0 0-.654-5.898c-.219-1.093-.438-1.966-.875-3.059-.437-.874-.872-1.966-1.965-2.621-.218 0-.44-.001-.44.217-1.31 3.277-3.494 6.12-5.898 8.086-2.403 1.966-5.462 2.84-8.521 3.058-3.06.219-6.338-.436-9.616-1.31-3.277-.874-6.552-1.968-9.83-3.06l-.439-.22c-.656-.218-1.526.002-1.963.44-1.748 2.185-3.06 4.149-4.59 6.334a58.435 58.435 0 0 0-2.84 5.027c-3.933-1.53-7.649-2.841-11.582-4.37-5.462-2.186-10.925-4.37-15.95-6.991-5.245-2.404-10.268-5.246-14.638-8.524-3.15-2.363-6.062-4.845-8.185-7.681l2.404-17.172z" fill="#f4511e" stroke-width="0" stroke-linejoin="round"/></g></symbol><symbol viewBox="0 0 24 24" id="handlebars" xmlns="http://www.w3.org/2000/svg"><path d="M8.55 10.32c-2.753 0-4.202 3.48-5.793 3.48-.98 0-1.126-.677-1.126-.915 0-.332.236-.706.564-.706.59 0 .414.77.414.77s.798-.555.272-1.298c-.42-.595-1.31-.623-1.92-.17-.617.458-1.057 1.146-.853 2.287.1.551.468 1.35 1.233 1.805.764.455 1.925.566 2.335.566 2.194 0 4.342-1.633 6.639-2.322a5.513 5.513 0 0 1 1.497-.222 6.19 6.19 0 0 1 1.92.226c2.296.689 4.444 2.323 6.638 2.323.41 0 1.57-.11 2.335-.566.765-.455 1.132-1.256 1.231-1.807.204-1.14-.235-1.829-.853-2.287-.61-.453-1.497-.423-1.918.172-.526.743.27 1.297.27 1.297s-.176-.77.414-.77c.329 0 .565.373.565.705 0 .238-.147.914-1.126.914-1.592 0-3.04-3.478-5.794-3.478-2.565 0-3.076 1.177-3.462 1.718-.004.005-.005.011-.008.016-.005-.006-.007-.013-.012-.02-.386-.54-.896-1.717-3.461-1.717z" fill="#ff7043" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 300.00001 300" id="haskell" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2.422"><path d="M23.928 240.5l59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z" fill="#ef5350"/><path d="M83.869 240.5l59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z" fill="#ffa726"/><path d="M228.72 188.08l-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924l-19.98-29.953h99.901v29.953z" fill="#ffee58"/></g></symbol><symbol viewBox="0 0 210 210" id="haxe" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -87)"><path fill="#f68712" stroke-width=".221" d="M42.78 191.545l63.431-63.43 63.431 63.43-63.431 63.431z"/><path d="M42.8 191.592L31.193 148.28 19.59 104.97 62.9 116.575l43.311 11.605-31.706 31.706z" fill="#fab20b" stroke-width=".266"/><path d="M105.956 128.111l-43.19-11.544-43.177-11.597 22.927.185 23.228.294 20.264 11.36z" fill="#fbc707" stroke-width=".265"/><path d="M19.59 104.97l11.596 43.176 11.545 43.19-11.303-19.948-11.36-20.263-.294-23.228z" fill="#fff200" stroke-width=".265"/><path d="M106.23 128.133l43.312-11.605 43.311-11.605-11.605 43.31-11.605 43.312-31.706-31.706z" fill="#f47216" stroke-width=".266"/><path d="M169.711 191.289l11.545-43.19 11.597-43.176-.185 22.927-.294 23.228-11.36 20.263z" fill="#f1471d" stroke-width=".265"/><path d="M192.853 104.923l-43.176 11.597-43.19 11.544 19.947-11.303 20.264-11.36 23.228-.293z" fill="#fbc707" stroke-width=".265"/><path d="M169.643 191.545l11.605 43.31 11.605 43.312-43.311-11.605-43.311-11.606 31.706-31.705z" fill="#f25c19" stroke-width=".266"/><path d="M106.487 255.025l43.19 11.544 43.176 11.598-22.927-.185-23.228-.294-20.264-11.36z" fill="#f68712" stroke-width=".265"/><path d="M192.853 278.167l-11.597-43.176-11.545-43.19 11.303 19.947 11.36 20.264.294 23.228z" fill="#f1471d" stroke-width=".265"/><path d="M106.211 254.976l-43.31 11.605-43.312 11.605 11.605-43.31L42.8 191.563l31.706 31.706z" fill="#f89c0e" stroke-width=".266"/><path d="M42.731 191.82l-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.263z" fill="#fff200" stroke-width=".265"/><path d="M19.59 278.186l43.175-11.597 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z" fill="#f25c19" stroke-width=".265"/></g></symbol><symbol viewBox="0 0 144 152" id="heroku" xmlns="http://www.w3.org/2000/svg"><path d="M118.68 13.279H26.865c-6.337 0-11.476 5.139-11.476 11.476V129.32c0 6.338 5.139 11.477 11.476 11.477h91.813c6.338 0 11.477-5.14 11.477-11.477V24.755c0-6.337-5.139-11.476-11.477-11.476zM44.08 121.669V96.165l14.346 12.752zm44.632 0v-38.08c-.063-2.976-1.496-6.551-7.97-6.551-12.966 0-27.51 6.52-27.654 6.586l-9.008 4.08V32.407h12.752v36.201c6.366-2.072 15.266-4.321 23.91-4.321 7.882 0 12.6 3.099 15.17 5.698 5.484 5.547 5.56 12.613 5.551 13.43v38.255zm3.188-68.54H79.149c5.011-6.576 8.158-13.496 9.564-20.723h12.751c-.86 7.243-3.796 14.187-9.563 20.722z" fill="#6963b9"/></symbol><symbol viewBox="0 0 24 24" id="hpp" xmlns="http://www.w3.org/2000/svg"><path d="M9.757 19.818H6.751v-5.882q0-2.381-1.737-2.381-.868 0-1.438.663-.56.662-.56 1.718v5.882H0V4.533h3.016v6.508h.037Q4.24 9.239 6.247 9.239q3.51 0 3.51 4.239z" stroke-width=".478" fill="#0277bd"/><path d="M13.073 11.448v2h-2v2h2v2h2v-2h2v-2h-2v-2zm7 0v2h-2v2h2v2h2v-2h2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="html" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.56l4.07-1.13.55-6.1H9.38L9.2 8.3h7.6l.2-1.99H7l.56 6.01h6.89l-.23 2.58-2.22.6-2.22-.6-.14-1.66h-2l.29 3.19L12 17.56M4.07 3h15.86L18.5 19.2 12 21l-6.5-1.8L4.07 3z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" id="http" xmlns="http://www.w3.org/2000/svg"><path d="M16.046 13.784c.074-.613.13-1.225.13-1.856s-.056-1.244-.13-1.856h3.137c.148.594.241 1.215.241 1.856a7.65 7.65 0 0 1-.241 1.856m-4.78 5.16c.557-1.03.984-2.144 1.281-3.304h2.738a7.452 7.452 0 0 1-4.019 3.304m-.232-5.16H9.828a12.314 12.314 0 0 1-.149-1.856c0-.631.056-1.253.149-1.856h4.343c.084.603.149 1.225.149 1.856 0 .63-.065 1.243-.149 1.856M12 19.315c-.77-1.113-1.393-2.348-1.773-3.675h3.545c-.38 1.327-1.002 2.562-1.773 3.675m-3.712-11.1h-2.71a7.353 7.353 0 0 1 4.01-3.304c-.557 1.03-.975 2.144-1.3 3.304m-2.71 7.425h2.71c.325 1.16.743 2.274 1.3 3.304a7.433 7.433 0 0 1-4.01-3.304m-.761-1.856a7.65 7.65 0 0 1-.241-1.856c0-.64.093-1.262.241-1.856h3.137c-.074.612-.13 1.225-.13 1.856 0 .63.056 1.243.13 1.856m4.046-9.253c.77 1.114 1.393 2.357 1.773 3.684h-3.545c.38-1.327 1.002-2.57 1.773-3.684m6.422 3.684h-2.738a14.523 14.523 0 0 0-1.28-3.304 7.412 7.412 0 0 1 4.018 3.304m-6.423-5.568c-5.132 0-9.28 4.176-9.28 9.28a9.28 9.28 0 0 0 9.28 9.282 9.28 9.28 0 0 0 9.281-9.281A9.28 9.28 0 0 0 12 2.647z" fill="#e53935" stroke-width=".928"/></symbol><symbol viewBox="0 0 24 24" id="image" xmlns="http://www.w3.org/2000/svg"><path d="M13.009 9.202h5.368l-5.368-5.368v5.368M6.177 2.37h7.808l5.856 5.856v11.711a1.952 1.952 0 0 1-1.952 1.952H6.178a1.951 1.951 0 0 1-1.952-1.952V4.322c0-1.083.868-1.952 1.952-1.952m0 17.567h11.71V12.13l-3.903 3.903-1.952-1.951-5.856 5.855M8.13 9.202a1.952 1.952 0 0 0-1.952 1.952 1.952 1.952 0 0 0 1.952 1.952 1.952 1.952 0 0 0 1.952-1.952A1.952 1.952 0 0 0 8.13 9.202z" fill="#26a69a" stroke-width=".976"/></symbol><symbol viewBox="0 0 512 512" id="ionic" xmlns="http://www.w3.org/2000/svg"><g fill="#4f8ff7"><path d="M423.592 132.804A31.855 31.855 0 0 0 429 115c0-17.675-14.33-32-32-32a31.853 31.853 0 0 0-17.805 5.409C344.709 63.015 302.11 48 256 48 141.125 48 48 141.125 48 256c0 114.877 93.125 208 208 208 114.873 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.196zM391.83 391.832c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089-25.94 0-51.095-5.077-74.767-15.089-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064C68.982 307.096 63.905 281.94 63.905 256c0-25.94 5.077-51.095 15.089-74.767 9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09 25.939 0 51.094 5.077 74.766 15.089a191.221 191.221 0 0 1 37.802 21.327A31.853 31.853 0 0 0 365 115c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191.085 191.085 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767 0 25.939-5.077 51.096-15.09 74.768-9.675 22.873-23.527 43.418-41.175 61.064z"/><circle cx="256.003" cy="256" r="96"/></g></symbol><symbol viewBox="0 0 24 24" id="java" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="javascript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="javascript-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#ffca28"/><path d="M2.444 2.506h14.135v14.136H2.444V2.506m3.714 11.811c.315.668.935 1.218 1.995 1.218 1.178 0 1.987-.629 1.987-2.003V8.993H8.805v4.508c0 .675-.275.848-.707.848-.455 0-.644-.314-.856-.683l-1.084.651m4.697-.14c.392.769 1.185 1.358 2.426 1.358 1.257 0 2.199-.652 2.199-1.854 0-1.107-.636-1.602-1.767-2.089l-.33-.141c-.573-.243-.816-.408-.816-.801 0-.322.243-.573.636-.573.377 0 .628.165.856.573l1.028-.683c-.432-.754-1.044-1.045-1.884-1.045-1.186 0-1.948.754-1.948 1.752 0 1.083.636 1.594 1.594 2.002l.33.141c.613.267.974.432.974.888 0 .377-.354.652-.903.652-.652 0-1.029-.338-1.312-.81l-1.083.63z" fill="#ffca28"/></symbol><symbol viewBox="0 0 180 180" id="jenkins" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="gia"><path transform="scale(1 -1)" fill="#37474f" d="M.899-144.42h144.42V0H.899z"/></clipPath></defs><g transform="matrix(1.0691 0 0 -1.0691 9.4 166.143)" clip-path="url(#gia)"><g fill-rule="evenodd"><path d="M107.96 30.661l-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066" fill="#f0d6b7"/><path d="M97.334-23.425l-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876" fill="#335061"/><path d="M23.238 85.687l8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005 1.251-10.63" fill="#6d6b6d"/><path d="M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253 7.19.626M26.676 87.875s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63 3.439 2.188" fill="#dcd9d8"/></g><g fill="#f7e4cd"><path d="M36.681 58.799l4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042M94.209 64.739s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468" fill-rule="evenodd"/><path d="M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625M82.327 99.129s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19"/><g fill-rule="evenodd"><path d="M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751.312-4.69M45.435 98.504s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382M117.97 122.27s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751"/><path d="M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404"/></g></g><g fill="#49728b" fill-rule="evenodd"><path d="M48.874 26.597L19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868"/></g><g fill-rule="evenodd"><path d="M100.15-23.739l11.567.313 2.814-28.764-11.881-1.563-2.5 30.014" fill="#335061"/><path d="M103.27-23.739l17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148l-6.565-26.887" fill="#335061"/><path d="M111.09-21.55l-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566" fill="#49728b"/><path d="M62.85-85.332l-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876-14.382 3.126" fill="#fff"/><path d="M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944" fill="#dcd9d8"/><path d="M115.06-49.691l12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56" fill="#fff"/><path d="M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25" fill="#dcd9d8"/></g><path d="M102.56-21.241L95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134" fill="#fff"/><path d="M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814 2.19-.938 6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313" fill="#dcd9d8" fill-rule="evenodd"/><path d="M65.664 25.968l-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627" fill="#fff"/><path d="M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752.938 1.251" fill="#dcd9d8" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43" fill="#d33833" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669" fill="#d33833" fill-rule="evenodd"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695" fill="#d33833" fill-rule="evenodd"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M102.87 10.649s-2.19 3.127-.626 4.065c1.564.938 3.127 0 4.065 1.563s0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93" fill="#ef3d3a" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99M119.79 101.4l.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626M82.63 101.29c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768M118.26 67.253c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63M37.668 71.387c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723M122.15 63.257c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091M82.149 59.745c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37M81.136 54.523c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645"/></g><path d="M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43" fill="#81b0c4" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37M143.21-30.661c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726M144.58-37.19c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346"/></g><g fill-rule="evenodd"><path d="M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808" fill="#dcd9d8"/><path d="M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378" fill="#f0d6b7"/><path d="M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423" fill="#81b0c4"/><path d="M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808zm-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752zm16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586zm25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035zM99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431zm-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11zm-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695zm-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123.04 1.011.078 2.021.119 3.032-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989.016.405.033.81.047 1.21-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a355.817 355.817 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505zm63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44zM27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576" fill="#231f20"/><path d="M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712" fill="#f7e4cd"/><path d="M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001M102.81-4.355a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0" fill="#1d1919"/></g><g><rect transform="matrix(.8 0 0 -.8 0 144)" x="16.854" y="177.38" width="70.412" height="4.12" rx=".983" ry=".983"/><rect transform="scale(1 -1)" x="78.502" y="-2.097" width="50.037" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="13.483" y="-3.697" width="54.831" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="83.296" y="-3.697" width="45.243" height="3.296" rx=".786" ry=".786"/></g></g></symbol><symbol viewBox="0 0 24 24" id="json" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#fbc02d"/></symbol><symbol viewBox="0 0 50 50" id="julia" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" stroke-width="5.673"><circle cx="13.497" cy="281.632" r="9.555" fill="#bc342d"/><circle cx="36.081" cy="281.632" r="9.555" fill="#864e9f"/><circle cx="24.722" cy="262.389" r="9.555" fill="#328a22"/></g></symbol><symbol viewBox="0 0 64 64" id="karma" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -233)"><path d="M38.556 288.413l-20.29-26.687 9.532-7.246 20.29 26.686h-.001.002l5.527 7.247z" fill="#359b8b" stroke-width=".173"/><path d="M35.681 241.172L24.92 255.327v-14.13H12.947v13.817l7.84 33.235h4.132v-13.147l.003.003 20.29-26.686-.008-.006 5.504-7.24H35.84v.12z" fill="#3cbeae" stroke-width=".206"/></g></symbol><symbol viewBox="0 0 24 24" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7 14a2 2 0 0 1-2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2m5.65-4A5.99 5.99 0 0 0 7 6a6 6 0 0 0-6 6 6 6 0 0 0 6 6 5.99 5.99 0 0 0 5.65-4H17v4h4v-4h2v-4H12.65z" fill="#26a69a"/></symbol><symbol viewBox="0 0 24 24" id="kivy" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.89 0 0 1.89 -12.157 -11.429)" fill="#90a4ae"><path d="M7.026 8.63v4.474l1.928-1.928a.437.437 0 0 0 0-.619zM9.38 16.072v-4.474l-1.927 1.927a.437.437 0 0 0 0 .62zM18.576 10.412l-5.346.564-.017.018 2.39 2.39zM9.922 8.502s.023 3.304-.003 4.452c-.02.856.371 1.114.746 1.507.538.564 1.599 1.57 1.599 1.57a.53.53 0 0 0 .75 0l1.843-1.844a.53.53 0 0 0 0-.75z"/></g></symbol><symbol viewBox="0 0 24 24" id="kl" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:#3aaae1}.b{fill:#fdfeff}</style></defs><title>kl</title><path d="M12.033 1.737c-.25-.003-.5.11-.729.337C8.225 5.15 5.15 8.227 2.078 11.31c-.144.144-.229.346-.341.521v.41c.16.223.294.474.485.666a3259.51 3259.51 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.428 3.046-3.041 6.09-6.083 9.133-9.127.47-.47.472-1.005.006-1.472l-9.218-9.217c-.23-.23-.48-.347-.731-.35zm-1.062 4.545l1.386.832c.702.422 1.403.846 2.109 1.262a.544.544 0 0 1 .04.026l.016.013.017.013c.061.056.089.123.088.224a510.281 510.281 0 0 0 0 3.794.463.463 0 0 1-.007.094c-.015.069-.054.103-.142.109a.464.464 0 0 1-.044.002c-.045-.002-.09-.002-.136-.003-.323-.006-.648-.001-.998-.001v-.527-1.34-.671-.003l.004-.668c0-.147-.039-.231-.17-.308-.893-.528-1.78-1.066-2.67-1.6-.051-.03-.101-.065-.173-.111l.001-.003h-.001zm.362 3.39c.068-.003.119.043.173.138.085.148.174.293.264.44l.015.025c.096.154.194.31.292.47l-1.915 1.176c-.337.207-.673.417-1.014.617-.113.067-.154.143-.154.277.01.977.01 1.955.014 2.932V16H7.7V16h-.002c-.004-.053-.014-.112-.014-.17-.005-1.25-.006-2.501-.015-3.751 0-.142.045-.222.164-.294a467.13 467.13 0 0 0 3.353-2.054l.016-.01a.606.606 0 0 1 .032-.017l.016-.008a.308.308 0 0 1 .033-.013l.012-.004a.157.157 0 0 1 .028-.005l.01-.001zm5.677 3.126l.314.54.346.594v.001c-.158.094-.298.178-.438.259l-3.097 1.798c-.106.062-.189.071-.3.01l-.893-.496-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001l.137-.25.495-.902 1.446.795c.442.243.886.483 1.323.734.121.07.212.072.334 0 .894-.525 1.792-1.043 2.689-1.563.057-.034.118-.061.191-.1z" fill="#29b6f6" stroke-width=".041"/></symbol><symbol viewBox="0 0 500 500" id="kotlin" xmlns="http://www.w3.org/2000/svg"><path d="M500 500H0V0h500L250 250z" fill="#7F52FF"></path></symbol><symbol viewBox="0 0 240 240" id="laravel" xmlns="http://www.w3.org/2000/svg"><path d="M216.05 119.036c-1.433.343-24.945 6.673-24.945 6.673l-19.227-28.622c-.537-.828-.99-1.656.359-1.849 1.345-.196 23.195-4.477 24.182-4.723.99-.245 1.837-.536 3.053 1.267 1.21 1.8 17.836 24.626 18.464 25.506.627.877-.447 1.41-1.883 1.748m-4.101 49.326c.588 1.003 1.176 1.64-.67 2.367-1.843.73-62.243 22.847-63.418 23.39-1.173.546-2.092.73-3.607-1.637-1.51-2.362-21.16-39.264-21.16-39.264l64.03-18.075c1.876-.644 2.317-.405 3.103.822 1.074 1.68 21.143 31.403 21.726 32.4m-103.7-21.087c-.78.202-37.566 9.733-39.525 10.22-1.965.485-1.965.246-2.188-.49-.226-.727-43.728-98.053-44.333-99.271-.605-1.214-.574-2.177 0-2.177.571 0 34.734-3.313 35.944-3.383 1.207-.07 1.08.205 1.526 1.033l49.025 91.818c.84 1.58 1.239 1.81-.452 2.248m94.588-59.77c-3.5-4.58-5.2-3.751-7.357-3.41-2.154.336-27.277 4.915-30.194 5.449-2.918.536-4.758 1.803-2.963 4.53 1.597 2.422 18.113 27.824 21.751 33.42l-65.663 17.066L66.18 49.832c-2.075-3.342-2.507-4.514-7.236-4.28-4.735.23-40.969 3.495-43.55 3.731-2.58.233-5.416 1.479-2.835 8.09 2.583 6.612 43.734 102.82 44.88 105.62 1.149 2.803 4.128 7.345 11.11 5.527 7.157-1.871 31.969-8.894 45.52-12.742 7.163 14.07 21.77 42.619 24.473 46.707 3.607 5.459 6.089 4.56 11.626 2.738 4.325-1.42 67.65-26.129 70.502-27.4 2.855-1.273 4.613-2.184 2.685-5.275-1.419-2.28-18.124-26.558-26.876-39.26 5.993-1.733 27.305-7.888 29.575-8.557 2.646-.779 3.008-2.19 1.572-3.94-1.436-1.755-21.293-28.72-24.79-33.296z" fill="#ff5722" stroke="#ff5722" stroke-width="8.852" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="less" xmlns="http://www.w3.org/2000/svg"><path d="M13.696 2.999V5h2.002v5a2 2 0 0 0 1.999 2 2 2 0 0 0-2 2v5h-2v2h2a2 2 0 0 0 2-2v-4a2 2 0 0 1 2-2h1V11h-1a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2.001zm-.03 12.766v.47a1 1 0 0 0 .03-.236 1 1 0 0 0-.03-.234zM10.566 21v-2.001H8.565v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2.001v-2H8.565a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-.999V13h1a2 2 0 0 1 2 2v3.999A2 2 0 0 0 8.564 21zm.03-12.766v-.47a1 1 0 0 0-.03.236 1 1 0 0 0 .03.234z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="lib" xmlns="http://www.w3.org/2000/svg"><path d="M19 7H9V5h10m-4 10H9v-2h6m4-2H9V9h10m1-7H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 40 40" id="livescript" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -257)" fill="#317eac"><path stroke-width="3.299" d="M5.419 260.18h3.685v34.207H5.419z"/><path stroke-width="3.299" d="M37.074 288.197v3.685H2.867v-3.685z"/><path stroke-width="2.894" d="M29.612 265.658l2.004 2.005L7.428 291.85l-2.004-2.005z"/><path stroke-width="2.325" d="M10.73 262.471h2.835v22.08H10.73z"/><path stroke-width="2.063" d="M15.36 262.519h2.835v17.382H15.36z"/><path stroke-width="1.77" d="M19.99 262.471h2.835v12.802H19.99z"/><path stroke-width="1.422" d="M24.526 262.491h2.835v8.254h-2.835z"/><path stroke-width="1.128" d="M28.783 262.463h2.835v5.197h-2.835z"/><path stroke-width="2.325" d="M34.801 286.545v-2.835h-22.08v2.835z"/><path stroke-width="2.063" d="M34.753 281.914v-2.835H17.371v2.835z"/><path stroke-width="1.77" d="M34.801 277.284v-2.835H21.999v2.835z"/><path stroke-width="1.422" d="M34.781 272.749v-2.835h-8.254v2.835z"/><path stroke-width="1.128" d="M34.809 268.492v-2.835h-5.197v2.835z"/></g></symbol><symbol viewBox="0 0 24 24" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="lua" xmlns="http://www.w3.org/2000/svg"><circle cx="12.203" cy="12.102" r="10.322" fill="none" stroke="#42a5f5"/><path d="M12.33 5.746a6.483 6.381 0 0 0-6.482 6.381 6.483 6.381 0 0 0 6.482 6.38 6.483 6.381 0 0 0 6.484-6.38 6.483 6.381 0 0 0-6.484-6.38zm1.86 1.916a2.329 2.292 0 0 1 2.33 2.293 2.329 2.292 0 0 1-2.33 2.291 2.329 2.292 0 0 1-2.329-2.29 2.329 2.292 0 0 1 2.328-2.294z" fill="#42a5f5" fill-rule="evenodd"/><ellipse cy="4.615" cx="19.631" rx="2.329" ry="2.292" fill="#42a5f5" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="markdown" xmlns="http://www.w3.org/2000/svg"><path d="M2 16V8h2l3 3 3-3h2v8h-2v-5.17l-3 3-3-3V16H2m14-8h3v4h2.5l-4 4.5-4-4.5H16V8z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" preserveAspectRatio="xMidYMid" id="markojs" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -120.96)" stroke-width=".984"><path d="M4.002 126.482c-.655 1.07-1.32 2.14-1.976 3.21-.655 1.06-1.308 2.142-1.963 3.212l.002.002-.002.002c.655 1.07 1.308 2.15 1.963 3.211.655 1.07 1.32 2.141 1.976 3.211h3.33c-.664-1.07-1.318-2.14-1.974-3.21-.653-1.069-1.307-2.145-1.961-3.214.654-1.068 1.308-2.146 1.961-3.215a601.93 601.93 0 0 1 1.974-3.209z" fill="#2196f3"/><path d="M3.999 126.482l-.002.002c.655 1.07 1.31 2.15 1.964 3.212.655 1.07 1.32 2.14 1.974 3.21h3.331c-.664-1.07-1.319-2.14-1.974-3.21-.653-1.068-1.306-2.146-1.96-3.214z" fill="#26a69a"/><path d="M15.203 126.482l.002.002c-.655 1.07-1.31 2.15-1.965 3.212-.655 1.07-1.319 2.14-1.974 3.21h-3.33c.664-1.07 1.318-2.14 1.973-3.21.654-1.069 1.307-2.146 1.961-3.214z" fill="#8bc34a"/><path d="M11.874 126.484c.664 1.07 1.318 2.14 1.974 3.21.653 1.068 1.307 2.146 1.961 3.214-.654 1.069-1.308 2.145-1.961 3.213-.656 1.07-1.31 2.14-1.974 3.21h3.33c.655-1.07 1.319-2.14 1.974-3.21.655-1.06 1.31-2.14 1.966-3.21l-.002-.003.002-.002c-.656-1.07-1.311-2.152-1.966-3.213-.655-1.07-1.319-2.138-1.974-3.209z" fill="#ffc107"/><path d="M16.74 126.482c.665 1.07 1.319 2.14 1.974 3.21.654 1.068 1.306 2.146 1.96 3.214-.654 1.069-1.306 2.145-1.96 3.213-.655 1.07-1.31 2.141-1.974 3.211h3.33c.656-1.07 1.32-2.14 1.974-3.21.655-1.062 1.31-2.141 1.966-3.212l-.002-.002.002-.002c-.655-1.07-1.31-2.152-1.966-3.213-.655-1.07-1.318-2.138-1.973-3.209z" fill="#f44336"/></g></symbol><symbol viewBox="0 0 23 24" id="mathematica" xmlns="http://www.w3.org/2000/svg"><path d="M11.512 1.523l-.073.025-.46.794-.454.763-1.217 2.09H9.29L5.435 3.5l-.1-.047h-.018v.092l.025.163v.086l.132 1.226v.082l.032.252v.082l.22 2.137v.075l.018.082v.06l-2.348.507-.04.015-.457.1-.025.01h-.042l-1.096.244-.04.007-.17.036v.082l.018.01 1.859 2.086.053.052.114.132.804.909v.005l-.053.05-.22.257-2.564 2.875-.01.007v.082l.071.006.295.075 1.697.366v.006l2.139.472h.015v.047l-.036.252v.08l-.046.412v.082l-.036.244v.082l-.045.412v.08l-.05.41v.08l-.036.244v.082l-.046.412v.082l-.05.407v.082l-.032.248V20l-.05.407v.104h.037l3.642-1.6.294-.134h.018l.177.312.539.911.015.032.854 1.465.16.262.404.695.007.022h.092l.005-.022.017-.025.56-.947.014-.042.6-1.033.316-.539.644-1.091.05.013 3.906 1.721h.035v-.085l-.138-1.32v-.082l-.032-.244v-.082l-.035-.245v-.085l-.033-.244v-.081l-.032-.245v-.082l-.032-.244v-.085l-.035-.245v-.082l-.032-.245v-.082l-.033-.244v-.085l-.025-.17v-.053l1.632-.354.043-.008.458-.107h.028v-.01l.23-.05.03-.01h.042l.382-.09.025-.01h.043l.194-.05h.033l1.015-.23.07-.007v-.064l-.015-.013-1.19-1.342-.028-.028-.197-.22-1.428-1.604v-.006l.295-.323.4-.457 2.148-2.408.015-.01v-.065l-.035-.008-1.288-.28-.372-.084-.047-.01-2.481-.544v-.045l.432-4.265v-.02h-.042l-.302.135-.01.014h-.025l-3.307 1.45-.297.135h-.015l-2.028-3.483-.099-.145-.014-.045zm-.001 1.114l1.365 2.323.34.592-.008.025-1.18 1.511-.517.66-.012-.01-.258-.335-.04-.05-1.397-1.787.03-.063 1.378-2.365.287-.491zm4.908 2.039l-.007.025-.168.225-.538.066zm-9.817.004l.053.02.677.3h-.499l-.224-.3zM16.947 5l-.123 1.248-.113-.928.226-.307zm-9.26.156l.053.024.705.309-.757-.175zm7.388.116l.02.168-1.318.403.003-.003.16-.071 1.015-.444zM9.669 6.388l.944 1.204v.01L9.483 7.2zm3.55.172l.21.682-.234.084-.089.022-.702.255.008-.022.776-.982zm-5 .836l.986.356.898.312.048.02 1.054.373.011 3.086-.362-.117-.67-.224-.081-.038-.735-.245-.77-.256-.29-.1-.011-.255-.032-1.195-.01-.287-.015-.894-.013-.297zm6.583 0l-.011.227-.028.9-.008.303-.032 1.475-.01.262-.337.117-.734.245-.77.256-.712.245-.355.117.01-3.086 1.632-.578zm.585.437l.09.735.79-.097-.915 1.302-.018.006.01-.183.018-.877zm-9.451.536l.152.22 1.447 2.049-2.607.968-.05.015-1.972-2.214-.28-.312.003-.01.115-.018.424-.1.14-.021.337-.078.042-.01zm11.146.003l3.284.713.029.01-.022.025-1.954 2.192-.277.312-.092-.036-2.564-.95.475-.681.152-.216zM6.787 8.52h.86l.036 1.258-.013-.006-.763-1.078zm1.358 2.625l.152.06.77.252.712.245.746.247.49.167-.065.092-1.723 2.334-1.015-.302-.082-.017-.035-.015-1.902-.56.938-1.22.981-1.277zm6.73 0l.033.006 1.787 2.327.132.17-.128.036-.032.014-2.196.642-.105.032-.564.17-.018-.003-1.053-1.44-.174-.239-.547-.726-.007-.018.469-.16.769-.254.713-.245.77-.252zm-7.766.305l-.007.02-.405.523-.291-.291.657-.245zm8.802 0l.043.007.578.212.714.27-.661.394-.375-.479-.03-.042-.262-.342zm-10.843.75l-.67.668.355-.397.207-.23zm12.911.016l.068.025.045.042.554.627.042.043.204.228-.255.135zm-6.473.265l.022.015 1.38 1.872.032.05.343.465.008.031-.088.117-.422.629-.047.074-.245.343-.97 1.43-.013.007-1.18-1.72-.096-.16-.493-.708-.008-.037 1.618-2.191.007-.01zm7.827 1.194l.565.633.063.082-.272-.093-.037-.013zm-15.785.148l.297.299-.637.218-.152.05.038-.058zm13.224.47l-.855.448.346.66-.185-.058-.27-.088-1.092-.348.012-.01zm-9.687.255l1.222.356-.006.007-.458.145-.443.135-.032.01-.49.157zm-2.765.048l.318.32 2.007.517-.567.18-.055.004-2.103-.469-.744-.156.007-.006zm14.966.205l.548.188v.003l-.457.1-.043.014-1.069.23zm-10.23.507l.007.227.01.347.025 1.363.025.691-.007.255-.24.107-2.863 1.255.032-.372.033-.255.017-.227.031-.256.037-.407.045-.42.018-.23.032-.251.032-.412.05-.414.013-.14 1.455-.457.003-.014.301-.098zm4.908 0l1.245.39v.014l.312.1 1.146.362.022.23.03.255.043.408.04.42.017.23.033.251.032.412.042.325.078.848-.078-.04-3.025-1.322-.004-.305.06-2.368zm-4.295.617l.015.007.067.107.6.875-.64.531-.034-1.438zm3.671 0h.008l-.005.06-.02.678-.005.214-.479-.223zm-2.888 3.605l.763.915.001.37-.017-.006-.025-.05-.464-.791-.012-.018zm1.53.61l.184.083-.343.586-.018.007.002-.532z" fill="#f44336" fill-rule="evenodd" stroke="#f44336" stroke-width=".7747499999999999" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 720 720" id="matlab" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><path d="M209.247 329.98L52.368 387.638l121.325 85.822 96.752-95.804-61.198-47.674z" fill="#4db6ac" fill-rule="evenodd" stroke-width=".3"/><path d="M480.193 71.446c-13.123 1.784-9.565 1.013-28.4 16.09-18.008 14.418-69.925 100.347-97.673 129.256-24.688 25.722-34.46 12.199-60.102 33.661-25.68 21.494-65.273 64.464-65.273 64.464l63.978 47.32L394.15 222.754c23.948-32.932 23.694-37.266 36.744-71.82 6.384-16.907 17.76-29.9 27.756-45.809 12.488-19.874 30.186-34.855 21.543-33.68z" fill="#00897b" fill-rule="evenodd" stroke-width=".3"/><path d="M478.206 69.796c-31.268-.189-62.068 137.245-115.56 242.691-54.543 107.519-162.235 176.82-162.235 176.82 18.156 8.243 34.681 4.91 54.236 23.394 13.375 16.164 52.09 95.976 75.174 146.117 0 0 18.964-10.297 42.994-27.695 24.03-17.397 53.124-41.896 73.384-70.3 26.883-37.692 47.897-61.043 65.703-75.271 17.806-14.23 32.404-19.336 46.458-20.54 50.238-4.305 124.582 85.792 124.582 85.792S527.267 70.09 478.206 69.796z" fill="#ffb74d" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 24 24" id="merlin" xmlns="http://www.w3.org/2000/svg"><text style="line-height:1.25;-inkscape-font-specification:'Century Gothic Bold'" x="1.953" y="21.178" transform="scale(.99582 1.0042)" font-weight="700" font-size="30.255" font-family="Century Gothic" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-width=".756"><tspan x="1.953" y="21.178" style="-inkscape-font-specification:'Century Gothic Bold'" font-size="22.745">M</tspan></text></symbol><symbol viewBox="0 0 192 191.99999" id="mocha" xmlns="http://www.w3.org/2000/svg"><title>Mocha Logo</title><g transform="translate(-354.75 -262.42) scale(4.835)" fill="#a1887f"><path d="M103.6 69.6c0-.5-.4-1-1-1H83.8c-.5 0-1 .4-1 1 0 3.4.5 15.1 5.5 20.8.2.2.4.3.7.3h8.4c.3 0 .5-.1.7-.3 5-5.6 5.5-17.3 5.5-20.8zm-7.4 18.2h-5.9c-.3 0-.5-.1-.7-.3-3.4-4-3.8-12-3.9-14.8 0-.5.4-1 1-1h13.2c.5 0 1 .4 1 1 0 2.8-.5 10.7-3.9 14.8-.3.2-.5.3-.8.3zM95.1 66.6s3.6-2.1 1.4-5.9c-1.3-2-1.9-3.7-1.4-4.4-1.3 1.6-3.5 3.3-1.1 6.9.8.9 1.2 2.8 1.1 3.4zM91.1 66.9s2.4-1.4.9-4c-.9-1.3-1.3-2.5-.9-2.9-.9 1.1-2.3 2.2-.7 4.7.5.5.7 1.8.7 2.2z"/><path d="M99.3 78.5c-.4 2.7-1.2 5.8-2.9 7.8-.2.2-.4.3-.6.3h-5c-.2 0-.5-.1-.6-.3-1.2-1.5-2-3.5-2.5-5.6 0 0 5.8.8 9.1-.4 2.4-.9 2.5-1.8 2.5-1.8z"/></g></symbol><symbol viewBox="0 0 24 24" id="movie" xmlns="http://www.w3.org/2000/svg"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4h-4z" fill="#ff9800"/></symbol><symbol viewBox="0 0 24 24" id="music" xmlns="http://www.w3.org/2000/svg"><path d="M16 9V7h-4v5.5c-.42-.31-.93-.5-1.5-.5A2.5 2.5 0 0 0 8 14.5a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V9h3m-4-7a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" fill="#ef5350"/></symbol><symbol viewBox="0 0 24 24" id="mxml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#ffa726"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#ab47bc" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#26c6da" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#e53935" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#9ccc65" stroke-width="12.914"/></symbol><symbol viewBox="0 0 24 24" id="nim" xmlns="http://www.w3.org/2000/svg"><path d="M4.464 15.75L2.288 3.78l5.985 7.617L12.08 3.78l3.809 7.617 5.985-7.617-2.177 11.97H4.464m15.234 3.264a1.088 1.088 0 0 1-1.088 1.088H5.553a1.088 1.088 0 0 1-1.089-1.088v-1.089h15.234z" stroke-width="1.088" fill="#ffca28"/></symbol><symbol viewBox="0 0 500 500" id="nix" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.965 36.302)" stroke-width=".395"><path d="M135.59 415.7c0-.295-2.752-5.283-6.116-11.084-3.364-5.801-6.116-10.776-6.116-11.055s9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694 0 .14-11.836.21-26.302.154-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.365.96h-13.229c-10.896 0-13.229-.095-13.229-.538zm167.58-125.61c-.134-.216 1.188-2.863 2.938-5.882 6.924-11.944 84.291-145.75 96.491-166.88 7.143-12.371 13.142-22.465 13.333-22.433.363.062 25.861 43.105 25.861 43.655 0 .174-6.761 11.952-15.026 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.564.564 30.213.564h29.996l.958 1.48c.526.814 3.296 5.547 6.155 10.518 2.859 4.971 5.45 9.29 5.756 9.597.706.705.704.724-.16 1.572-.395.388-3.36 5.323-6.587 10.965-3.228 5.643-6.056 10.387-6.285 10.543-.23.156-19.695.171-43.256.034l-42.84-.249-.804 1.15c-.441.632-7.504 12.736-15.696 26.897l-14.892 25.747H339.03c-8.517 0-20.015.116-25.55.259-6.55.168-10.15.121-10.309-.135zM169.42 132.23c-56.373-.055-102.5-.182-102.5-.282 0-.1 5.617-10.132 12.481-22.294l12.481-22.112h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427-8.139-14.199-14.797-25.917-14.797-26.04 0-.123 2.682-4.853 5.96-10.51s6.003-10.578 6.055-10.934c.086-.586 1.376-.648 13.572-.648 7.413 0 13.463.143 13.446.317-.017.174.222.707.531 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.118.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z" fill="#5075c1"/><path d="M25.289 203.14c-6.098 10.563-6.69 11.711-6.225 12.078.283.224 3.18 5.044 6.44 10.712 3.261 5.668 6.017 10.355 6.124 10.417.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.472.273-1.08 3.094-14.637 26.574L62.06 289.793l12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956.086.05 23.257-39.831 51.492-88.624 11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.097-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.024.037c-.011.003-1.62-.001-1.825 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.568.03-1.147-.03-1.29-.132-.142-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.199 21.131 89.393 155.03 96.276 167 1.502 2.613 2.92 4.803 3.443 5.348.9-1.249 3.531-5.63 7.954-13.219a1342.88 1342.88 0 0 1 10.049-17.76l6.606-11.443c.692-1.403.754-1.818.653-2.117-.162-.48-6.904-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.991-26.32a.73.73 0 0 1-.009-.366l-.426-.913L359.42 72.5c3.69-6.307 6.425-11.042 9.47-16.29 9.159-15.948 12.037-21.189 11.896-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.145.035-.42.112-5.454.195-11.189.185-5.734-.01-11.22.024-12.188.073l-1.76.089-14.997 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.272.05-.255-.148-7.143-11.902-15.306-26.119l-14.36-25.016c-.115-.186-.444-.744-.457-.752-.477-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.042.48.329 1.05 25.338 43.735 25.772 43.985.207.119 14.178.239 31.05.266 26.651.044 30.75.152 31.234.832.308.43 9.988 17.214 21.513 37.296s21.152 36.627 21.394 36.767c.242.14 5.927.243 12.633.23 6.706-.013 12.401.099 12.657.246.132.076.382-.141.852-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.441-9.774-.195-.712-.065-.822 1.156-.98 1.956-.252 57.397-.057 58.07.205.238.092.79-.569 2.594-3.497 1.866-3.067 5.03-8.524 11-18.866 7.22-12.505 13.044-22.784 12.942-22.843-.102-.059-.771-.051-1.489.016l-.046.001c-4.452.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.121.01-2.095.076-2.66.172a.25.25 0 0 0-.062.083z" fill="#7db7e1"/></g></symbol><symbol viewBox="0 0 24 24" id="nodejs" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.85c-.27 0-.55.07-.78.2l-7.44 4.3c-.48.28-.78.8-.78 1.36v8.58c0 .56.3 1.08.78 1.36l1.95 1.12c.95.46 1.27.47 1.71.47 1.4 0 2.21-.85 2.21-2.33V8.44c0-.12-.1-.22-.22-.22H8.5c-.13 0-.23.1-.23.22v8.47c0 .66-.68 1.31-1.77.76L4.45 16.5a.26.26 0 0 1-.11-.21V7.71c0-.09.04-.17.11-.21l7.44-4.29c.06-.04.16-.04.22 0l7.44 4.29c.07.04.11.12.11.21v8.58c0 .08-.04.16-.11.21l-7.44 4.29c-.06.04-.16.04-.23 0L10 19.65c-.08-.03-.16-.04-.21-.01-.53.3-.63.36-1.12.51-.12.04-.31.11.07.32l2.48 1.47c.24.14.5.21.78.21s.54-.07.78-.21l7.44-4.29c.48-.28.78-.8.78-1.36V7.71c0-.56-.3-1.08-.78-1.36l-7.44-4.3c-.23-.13-.5-.2-.78-.2M14 8c-2.12 0-3.39.89-3.39 2.39 0 1.61 1.26 2.08 3.3 2.28 2.43.24 2.62.6 2.62 1.08 0 .83-.67 1.18-2.23 1.18-1.98 0-2.4-.49-2.55-1.47a.226.226 0 0 0-.22-.18h-.96c-.12 0-.21.09-.21.22 0 1.24.68 2.74 3.94 2.74 2.35 0 3.7-.93 3.7-2.55 0-1.61-1.08-2.03-3.37-2.34-2.31-.3-2.54-.46-2.54-1 0-.45.2-1.05 1.91-1.05 1.5 0 2.09.33 2.32 1.36.02.1.11.17.21.17h.97c.05 0 .11-.02.15-.07.04-.04.07-.1.05-.16C17.56 8.82 16.38 8 14 8z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 300 300" id="nodemon" xmlns="http://www.w3.org/2000/svg"><title>nodemon</title><path d="M149.868 20.62c-2.124 0-4.25.55-6.154 1.648L41.899 81.083a12.306 12.306 0 0 0-6.15 10.652v117.633a12.29 12.29 0 0 0 6.152 10.646l101.815 58.766h.001a12.282 12.282 0 0 0 12.291 0l101.84-58.766a12.29 12.29 0 0 0 6.153-10.652V91.738a12.31 12.31 0 0 0-6.146-10.652L156.015 22.27a12.302 12.302 0 0 0-6.153-1.648zM83.303 70.93s11.789 33.031 35.477 31.934l27.74-15.961a7.348 7.348 0 0 1 3.414-.99h.641a7.233 7.233 0 0 1 3.404.99l27.738 15.961c23.69 1.094 35.475-31.934 35.475-31.934 5.233 23.154 1.06 38.641-5.924 48.942l4.541 2.614h.002c2.321 1.327 3.734 3.795 3.737 6.49l-.12 95.811a3.724 3.724 0 0 1-1.855 3.227 3.624 3.624 0 0 1-3.735 0L177.1 206.971c-2.311-1.363-3.742-3.818-3.742-6.48v-44.763a7.44 7.44 0 0 0-3.737-6.465l-15.642-9.01a7.28 7.28 0 0 0-3.715-1.01 7.378 7.378 0 0 0-3.742 1.01l-15.648 9.01c-2.316 1.323-3.729 3.798-3.729 6.467v44.762c0 2.663-1.413 5.1-3.738 6.48l-36.748 21.041a3.571 3.571 0 0 1-3.71 0c-1.173-.65-1.864-1.887-1.864-3.224l-.137-95.812a7.483 7.483 0 0 1 3.74-6.49l4.541-2.615c-6.982-10.302-11.16-25.79-5.925-48.942z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 990 990" id="npm" xmlns="http://www.w3.org/2000/svg"><defs><style>.hncls-1{fill:#cb3837}.cls-2{fill:#fff}</style></defs><title>n</title><path class="hncls-1" d="M113.26 876.74V113.27h763.47v763.47zm143.59-620.4v476.18h240.61V355.63h140.21v376.96h95.457V256.34z" fill="#e53935" stroke-width=".771"/></symbol><symbol id="nunjucks" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.host0{fill:#388e3c}</style><path class="host0" d="M11.2 21.1H8.1l-2.3-7.9v7.9H2.7V2.9h3.1l2.3 7.4V2.9h3.1zM21.3 19.2c0 1-.8 1.9-1.9 1.9h-4.8c-1 0-1.9-.8-1.9-1.9v-3.8l3.2-.7V18h2.3V7.2h3.1v12z"/></symbol><symbol viewBox="0 0 150 150.00001" id="ocaml" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.76136 0 0 .76136 11.616 19.98)"><path d="M83.02 101.645l.023-.062c-.035-.159-.047-.195-.024.062z" fill="none" stroke-width="1.028"/><linearGradient id="hpa" gradientUnits="userSpaceOnUse" x1="-696.735" y1="97.7" x2="-696.735" y2="142.997" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M82.313 138.79c-.471-1.004-1.904-3.621-2.624-4.46-1.562-1.828-1.927-1.966-2.386-4.275-.799-4.02-2.913-11.31-5.405-16.341-1.286-2.596-3.426-4.777-5.385-6.66-1.71-1.652-5.565-4.431-6.237-4.294-6.296 1.257-8.249 7.432-11.21 12.323-1.638 2.705-3.374 5.007-4.665 7.885-1.192 2.646-1.087 5.577-3.128 7.849-2.093 2.333-3.454 4.814-4.48 7.829-.194.574-.747 6.596-1.348 8.015l9.357-.659c8.719.594 6.2 3.936 19.81 3.208l21.487-.665c-.666-1.97-1.584-4.25-1.938-4.991-.599-1.248-1.352-3.69-1.848-4.763z" fill="url(#hpa)" stroke-width="1.028"/><linearGradient id="hpb" gradientUnits="userSpaceOnUse" x1="-666.972" y1="142.12" x2="-666.972" y2="142.12" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><linearGradient id="hpc" gradientUnits="userSpaceOnUse" x1="-675.228" y1="-1.28" x2="-675.228" y2="142.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M109.553 94.296c-1.652 1.193-4.88 4.06-11.902 5.145-3.152.487-6.1.527-9.335.365-1.584-.076-3.077-.157-4.665-.177-.936-.008-4.074-.107-3.919.193l-.349.871c.054.287.169 1.004.2 1.177.129.704.165 1.265.192 1.912.048 1.331-.11 2.719-.043 4.062.141 2.787 1.175 5.326 1.306 8.137.143 3.13 1.69 6.442 3.188 8.998.569.973 1.434 1.084 1.811 2.283.442 1.373.024 2.83.239 4.293.842 5.675 2.477 11.606 5.032 16.728.018.043.038.09.06.128 3.156-.53 6.318-1.665 10.418-2.271 7.517-1.115 17.972-.54 24.688-1.17 16.993-1.597 26.216 6.97 41.478 3.459V22.459c0-11.84-9.594-21.438-21.435-21.438H19.239C7.4 1.021-2.197 10.62-2.197 22.458v46.774c3.067-1.11 7.479-7.635 8.861-9.222 2.419-2.775 2.858-6.315 4.062-8.544 2.743-5.078 3.215-8.57 9.451-8.57 2.907 0 4.061.67 6.027 3.31 1.368 1.834 3.731 5.224 4.837 7.49 1.277 2.615 3.357 6.153 4.272 6.867.677.53 1.35.928 1.976 1.163 1.012.38 1.848-.316 2.525-.855.863-.687 1.235-2.088 2.035-3.957 1.152-2.696 2.408-5.926 3.122-7.054 1.237-1.949 1.658-4.261 2.993-5.381 1.97-1.652 4.54-1.768 5.246-1.908 3.957-.781 5.755 1.906 7.704 3.645 1.276 1.138 3.019 3.432 4.256 6.507.967 2.4 2.199 4.622 2.714 6.008.497 1.339 1.725 3.484 2.453 6.055.661 2.336 2.43 4.125 3.102 5.235 0 0 1.029 2.882 7.285 5.516 1.357.572 4.1 1.501 5.736 2.096 2.718.988 5.351.86 8.704.458 2.391 0 3.686-3.462 4.772-6.234.643-1.639 1.259-6.334 1.678-7.667.406-1.297-.544-2.3.265-3.437.946-1.327 1.508-1.399 2.054-3.129 1.172-3.704 7.95-3.89 11.761-3.89 3.176 0 2.772 3.083 8.16 2.028 3.086-.605 6.059.398 9.335 1.265 2.758.732 5.352 1.566 6.906 3.385 1.005 1.178 3.5 7.08.958 7.331.244.3.423.84.88 1.135-.566 2.226-3.03.64-4.4.355-1.845-.383-3.147.057-4.952.856-3.085 1.374-7.598 1.214-10.286 3.452-2.281 1.898-2.277 6.133-3.34 8.507-.002-.001-2.955 7.6-9.402 12.248z" fill="url(#hpc)" stroke-width="1.028"/><linearGradient id="hpd" gradientUnits="userSpaceOnUse" x1="-735.137" y1="90.833" x2="-735.137" y2="141.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M38.247 105.09c-1.467-.15-2.83-.317-4.256-.605-2.662-.536-5.57-1.06-8.193-1.688-1.592-.385-6.895-2.263-8.048-2.792-2.702-1.246-4.496-4.63-6.609-4.282-1.348.22-2.662.682-3.5 2.042-.685 1.11-.917 3.016-1.391 4.294-.55 1.485-1.5 2.87-2.331 4.284-1.53 2.595-4.282 4.941-5.468 7.469-.239.52-.45 1.101-.649 1.708V144.415a48.57 48.57 0 0 1 4.45.96c11.955 3.19 14.872 3.46 26.598 2.119l1.1-.146c.897-1.867 1.59-8.227 2.171-10.195.454-1.51 1.077-2.712 1.313-4.253.223-1.463-.02-2.858-.146-4.188-.329-3.332 2.427-4.522 3.742-7.384 1.186-2.589 1.871-5.535 2.853-8.181.941-2.54 2.41-6.13 4.918-7.408-.305-.355-5.237-.518-6.554-.65z" fill="url(#hpd)" stroke-width="1.028"/></g></symbol><symbol viewBox="0 0 24 24" id="pdf" xmlns="http://www.w3.org/2000/svg"><path d="M14 9h5.5L14 3.5V9M7 2h8l6 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.853.853 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71l.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M8.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69l.48-.31m3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82-.05.04z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="perl" xmlns="http://www.w3.org/2000/svg"><path d="M12 14c-1 0-3 1-3 2 0 2 3 2 3 2v-1a1 1 0 0 1-1-1 1 1 0 0 1 1-1v-1m0 5s-4-.5-4-2.5c0-3 3-3.75 4-3.75V11.5c-1 0-5 1.5-5 4.5 0 4 5 4 5 4v-1M10.07 7.03l1.19.53c.43-2.44 1.58-4.06 1.58-4.06-.43 1.03-.71 1.88-.89 2.55C13.16 3.55 15.61 2 15.61 2a15.916 15.916 0 0 0-2.64 3.53c1.58-1.68 3.77-2.78 3.77-2.78-2.69 1.72-3.9 4.45-4.2 5.21l.55.08c0 .52 0 1 .25 1.38C14.1 11.31 18 11.47 18 16s-4.03 6-6.17 6C9.69 22 5 21.03 5 16s4.95-5.07 5.83-7.08c.12-.38-.76-1.89-.76-1.89z" fill="#9575cd"/></symbol><symbol viewBox="0 0 24 24" id="php" xmlns="http://www.w3.org/2000/svg"><path d="M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09-.28.22-.71.33-1.29.33h-.87l.53-2.76h.99m-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59l-1.28 6.58m7.25-8.33l-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18.11.12.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35h-1.44M18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76h1m-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75l-1.29 6.58z" fill="#1E88E5"/></symbol><symbol viewBox="0 0 79 78" id="postcss" xmlns="http://www.w3.org/2000/svg"><title>postcss-logo-symbol</title><g transform="translate(5.48 5.52) scale(.85425)" fill="#e53935" fill-rule="evenodd" stroke="#e53935" stroke-width="1.519"><path d="M15.447 32.623c.106.08.29.132.106.29-.132.184-.29.342-.395.553-.105.185-.184.237-.342.106.21-.343.42-.66.63-.95zM68.342 60.24c0 .078.026.13.026.21.053-.105.053-.158.08-.21zm0 .236v-.026zm-5.368 10.277l-4.58-25.402c-.078-.025-.183-.077-.368-.13.053.105.08.184.106.263.13-.026.184-.026.236-.052 0-.026 0-.052.027-.08l4.58 25.404zm-4.737-31.12c-.026.078-.026.158-.026.237 0-.08 0-.16.028-.238zm.026.526c-.026 0-.026 0-.052-.028v.026c.028.026.028.026.054 0zm-.052.21v-.185c-.077.026-.156.026-.262.053.132.05.264.078.264.13z"/><path d="M78.71 33.967c-.052-1.028-.078-2.056-.184-3.083-.184-1.397-.368-2.82-.684-4.19-.237-1.133-.63-2.214-1.026-3.294-.5-1.265-1-2.556-1.632-3.768-1.026-1.95-2.368-3.69-3.605-5.508-.818-1.16-1.87-2.108-2.66-3.294-.447-.685-1.105-1.264-1.763-1.79-1.053-.845-2.158-1.61-3.263-2.347a32.525 32.525 0 0 0-2.58-1.634c-.71-.397-1.473-.713-2.21-1.056-.842-.395-1.658-.87-2.605-1.054-.238-.05-.448-.13-.685-.21-.605-.21-1.184-.447-1.79-.632-.92-.29-1.815-.632-2.763-.87C50.342 1 49.394.843 48.446.71 47.394.555 46.316.5 45.262.397a26.83 26.83 0 0 0-2.026-.184C42.236.16 41.21.16 40.21.134c-.5-.027-1.026-.08-1.526-.053-.763.026-1.526.105-2.29.21-.736.08-1.473.21-2.183.317-.867.105-1.735.158-2.604.264-.816.106-1.658.264-2.473.396-.29.053-.58.158-.87.21-.63.132-1.288.185-1.92.396-1.13.344-2.263.74-3.368 1.16-1.027.422-2.027.87-3 1.397-1 .552-1.948 1.21-2.895 1.844a45.325 45.325 0 0 0-2.66 1.923c-.84.66-1.63 1.397-2.394 2.135-.42.42-.763.922-1.158 1.396-.657.765-1.315 1.502-1.947 2.293-.524.66-1 1.344-1.5 2.03-.893 1.21-1.656 2.502-2.366 3.794-.29.527-.553 1.054-.816 1.58-.395.79-.816 1.555-1.184 2.372-.264.554-.474 1.16-.632 1.766-.367 1.292-.736 2.61-1.078 3.9-.316 1.16-.395 2.372-.42 3.558-.027 1.054.078 2.082.183 3.136.027.264-.13.58.184.79-.105.29-.026.45.13.5-.182.29.08.476-.024.74-.027.052.08.157.13.236 0 .08-.025.185 0 .264.028.237.133.474.133.738 0 .184.157.395.21.58.026.078 0 .21-.053.263-.158.184-.132.342.105.448.133.342.08.5.054.66.052.236-.027.315 0 .368.21.422.29.896.315 1.37 0 .106.053.212.106.343.026 0 0 .5 0 .5.13-.078.237-.104.368-.157.08.342.158.66.263.95.132.21.132.314.08.34.105.474.157.922.34 1.37 0-.5-.05-1-.13-1.475.368.132.684.263.895.263.027-.08.053-.184.08-.237-.158-.157-.29-.394-.448-.552.053.21 0 .29 0 .37-.105-.054-.237-.107-.368-.16.105-.13.21-.263.368-.42 0-.238-.13-.45-.5-.423.158-.052.316-.13.5-.184.29-.157-.026-.447-.026-.816.026-.447-.237-.895-.316-1.37-.132-.737-.105-1.844-.184-2.582-.158-.132-.29.21-.316.237.08.632.158 1.264.21 1.897-.157-.527-.263-1.107-.394-1.74-.027.185-.053.264-.053.37-.13.13-.026.29.053.474-.184-.08-.395-.052-.395-.052v.738c-.262-.264-.34-.474-.473-.66-.052-.21-.08-.42-.13-.63.05-.133 0-.212 0-.29a15.968 15.968 0 0 1-.08-.634c.026-.026-.026-.42-.026-.42.21.025.343.05.474.05-.263-.34-.08-.552.027-.763.053-.106.237-.13.29-.238.21-.395.553-.71.553-1.212 0-.237.08-.5.105-.738.053-.448.105-.896.13-1.344.054-.58 0-1.16.133-1.713.212-.92.475-1.843.764-2.766.21-.66.448-1.29.71-1.95.395-1.028.764-2.056 1.264-3.03.71-1.424 1.526-2.794 2.316-4.19.5-.87 1.026-1.687 1.58-2.53.525-.817 1.05-1.66 1.657-2.425a21.452 21.452 0 0 1 2.79-2.978c1.053-.948 2.053-1.923 3.184-2.793a32.218 32.218 0 0 1 4.685-3.005c1.343-.71 2.737-1.266 4.132-1.793.895-.342 1.868-.5 2.79-.79 1.052-.343 2.105-.5 3.21-.527.71-.027 1.395-.106 2.105-.185.632-.05 1.263-.104 1.948-.183-.08.105-.106.158-.132.21-.288.422-.604.844-.894 1.265-.237.343-.5.712-.737 1.054-.422.555-.87 1.108-1.264 1.688-.605.87-1.158 1.766-1.79 2.635-.63.843-1.315 1.634-1.973 2.45-.868 1.134-1.684 2.293-2.552 3.426-.79 1.08-1.63 2.11-2.394 3.19-.684.947-1.29 1.95-1.948 2.923-.973 1.45-1.947 2.872-2.92 4.322a271.93 271.93 0 0 1-2.316 3.294c-.053.08-.132.104-.21.157-.21.342-.21.527-.29.685-.21.395-.42.79-.658 1.16-.132.21-.316.394-.474.605-.026-.316.42-.474.21-.87-.13.212-.263.396-.394.607l-.316.63c.105.08.29.133.105.29-.08.133-.158.29-.237.423a.954.954 0 0 0 .29-.264c0 .29-.158.526-.29.763-.105.21-.368.37-.552.527.026.027.21.106.237.132.237-.08.316-.21.343-.132.08-.105.158-.184.184-.263.104-.264.262-.474.525-.58.106-.053.184-.132.263-.21.79-.818 1.606-1.608 2.316-2.478 1.106-1.345 2.106-2.74 3.16-4.11.446-.58.973-1.16 1.446-1.714.078.606.026 1.185 0 1.74-.08.974-.132 1.95-.21 2.95-.027.395 0 .79-.027 1.186 0 .105-.08.184-.08.29 0 .263.08.553.08.817-.08.975-.186 1.923-.265 2.898-.027.21.078.422.13.607-.13 1.422.16 2.925-.078 4.427.184-.29.237-.474.237-.658.025-.158 0-.316 0-.5v-.264c.025-.475.13-.975.078-1.45-.053-.527-.053-1.027.053-1.528.053-.21-.026-.474.106-.738v.395c-.026 1.5.027 3.003-.183 4.505-.027.132.08.37-.21.343-.238.474.052.817-.21 1.08-.054.053.05.29.077.448-.106.317-.106.317.052.343.026.58.08 1.106.105 1.66.42-1 .21-2.03.396-3.058.026.422.053.844.026 1.29 0 .687-.026 1.345-.052 2.03 0 .132-.027.264-.053.396-.08.37-.105.738-.237 1.08-.105.264-.052.66-.052.975v1.003c.105.448-.027.685.052.948-.08.265-.105.344-.08.423l.08.395c.527-.053.29.343.5.553-.158.212-.105.29-.105.397 0 .237-.025.448-.052.685 0 .606-.026 1.212-.026 1.792 0 .08.026.157.026.236 0 .054-.026.74-.026.74.053.078 0 .157-.08.236-.025 0-.104-3.347-.104-3.347h-.395c-.052 1.58.08 3.003-.21 4.48-.316.025-.42.078-.764.078-.816 0-1.632 0-2.448.026-.974 0-1.92.026-2.895.026-.472 0-.972.054-1.446.054-.632 0-1.29-.08-1.92-.08-.975 0-1.922.08-2.896.106-.71.026-1.42.026-2.13.053-.475.025-.95.05-1.422.104-.21.026-.395.105-.658.184-.08 0-.263-.026-.42 0-.265.053-.5.21-.765.264-.395.08-.5.184-.448.58v.263c-.026.052.58-.08.58-.08-.054 0-.08.158-.16.29.212-.08.343-.132.475-.184.395.185.737.08 1.052.16 1.026.262 2.078.37 3.13.473.685.053 1.343.08 2.027.105.973.053 1.947.106 2.92.106.816 0 1.606-.08 2.42-.08 1.13 0 2.264.052 3.395.08.237 0 .5-.028.763-.028h1.92c1.712-.052 3.422-.08 5.133-.13.975-.028 1.975-.08 2.948-.107l3-.08c1.158-.026 2.316-.026 3.448-.05.868 0 1.71-.03 2.58-.055.972-.026 1.972-.105 2.946-.157.527-.027 1.054-.08 1.58-.132.632-.052 1.29-.13 1.92-.157.948-.054 1.922-.08 2.87-.133 1.184-.078 2.368-.183 3.578-.21 1.106-.052 2.237-.026 3.343-.052.974-.027 1.948-.08 2.948-.106l1.66-.08s1.104-.026 1.657-.08c.947-.052 1.894-.157 2.842-.183.604-.027 1.21 0 1.815-.027.973-.026 1.973-.08 2.947-.08.367 0 .762.054 1.236.08-.21.185-.342.29-.5.422.105.026.21.08.316.132a.71.71 0 0 1-.42.13c-.054.133-.107.186-.16.45h.474c-.184 0-.342.237-.526.395-.21-.054-.395 0-.5.29.184.104.158.183.132.29-.316.104-.553.21-.42.552-.107.052-.238.105-.37.184-.13.21-.368.263-.316.553.106.025.21.08.29.104-.132.053-.263.132-.395.184-.473.29-.262.422-.157.554-.08.053-.158.105-.237.132.052.237.13.29.157.29a9.3 9.3 0 0 0-.395.316c-.08.237-.185.342-.29.5s-.158.37-.29.527c-.552.607-.947 1.32-1.657 1.793-.264.185-.5.422-.737.66-.474.447-.895.948-1.395 1.37a29.595 29.595 0 0 1-2.052 1.554 151.56 151.56 0 0 1-2.604 1.792c-.474.315-1 .552-1.5.842s-.974.554-1.474.843c-.316.21-.606.5-.948.66-.868.37-1.79.685-2.684 1.028-.87.37-1.5.685-2.158.922-.605.21-1.237.37-1.868.5-.21.054-.448 0-.685.027-.448.08-.895.186-1.343.238-1.158.158-2.316.264-3.473.422-.685.08-1.343.21-2.027.29-.473.026-.973-.026-1.447-.026-.342 0-.71.08-1.053.027-.552-.08-1.105-.21-1.658-.316-.13-.026-.316-.08-.42-.026-.21.106-.396-.052-.607 0-.13.027-.262-.08-.394-.08-.106-.025-.238.028-.37 0-.29-.078-.552-.183-.87-.157-.313.026-.63-.132-.97-.21-.475-.106-.92-.21-1.396-.317a2.38 2.38 0 0 1-.525-.237c-.685 0-1.133-.026-1.554-.185-.368-.13-.71-.315-1.105-.262-.104.026-.183-.026-.29-.026-.08-.106-.157-.317-.235-.317-.526.027-.842-.42-1.29-.553-.236-.08-.42-.343-.657-.422-.58-.237-1.052-.737-1.71-.816-.21-.027-.42-.132-.658-.21.08.104.13.183.21.262-.763-.37-1.473-.79-2.184-1.186-.104-.026-.183-.13-.262-.184l-.71-.474c-.395.08-.553-.08-.66-.132-.71-.5-1.525-.817-2.21-1.37-.29-.238-.63-.396-.84-.686-.37-.448-.817-.764-1.317-1.027-.394-.21-.762-.448-1.13-.685-.185-.132-.37-.29-.37-.58 0-.185-.078-.37-.315-.264-.105-.158-.21-.342-.342-.395-.316-.13-.526-.37-.763-.58s-.42-.5-.71-.605c-.527-.21-.843-.658-1.158-1.027-.738-.87-1.396-1.82-2.08-2.74-.053-.08-.158-.133-.237-.212.105.29.237.527.368.79-.262-.105-.446-.29-.604-.474-.027.027 1.815 3.057 1.815 3.057.16.237.29.475.448.712a.813.813 0 0 1-.79-.422c-.236-.42-.5-.684-1.026-.63a4.588 4.588 0 0 1-.13-.58c-.107 0-.185 0-.37-.027.37.58.685 1.08 1.027 1.66-.133-.08-.21-.132-.265-.158.473.5.815 1.133 1.42 1.45.132.605.816.895.974 1.475-.13-.027-.238-.053-.37-.08-.21-.263-.447-.526-.683-.816.052.184.13.342.236.474.316.395.606.79.974 1.133.132.134.316.187.316.424.21.105.29.13.368.13.054.16-.025.397.29.344.21.395.42.395.71.264.343.343.528.37.764.16 0 .13.026.262.026.368.105-.053.08-.132.08-.264.13.105.21.158.262.21.263.37.5.712.868 1.002.5.422.948.87 1.42 1.265.922.765 1.95 1.398 2.975 1.977 1.264.712 2.475 1.476 3.764 2.16 1.552.818 3.21 1.372 4.92 1.767.632.132 1.237.263 1.87.42.55.16 1.104.397 1.657.528.842.185 1.71.343 2.552.5.183.027.37.054.58.08.235.053.524-.053.577.027.132.21.237.104.395.078.184-.053.395-.053.605-.053.737.026 1.447.184 2.184.132.16 0 .396-.133.528.13.236-.105.368-.105.473-.13.028.236 0 .236-.05.262-.054.026-.133.053-.238.132.947.184 1.842.21 2.63 0 1.37.105 2.554-.053 3.686-.448.105.132.184.316.342.053.052-.08.184-.107.29-.133.236-.053.526-.158.736-.08.238.08.317-.13.5-.13.317 0 .606-.027.896-.08.158-.026.316-.105.5-.158a1.285 1.285 0 0 0-.58-.133c.317-.158.606-.29.896-.42-.053.078-.106.183-.21.183h.367c-.08 0-.185.237-.316.395.946-.237 1.814-.448 2.657-.66-.29-.552.315-.367.526-.684-.263.08-.526.158-.79.21.895-.447 1.816-.842 2.71-1.237-.13.158-.29.237-.525.37.158.025.263.025.342.05.42.133.316-.262.447-.5.5 0 .71-.078.947-.158.263-.08.526-.158.79-.263.42-.184.815-.42 1.236-.63.08-.028.21 0 .316 0 .29-.186.394-.344.473-.318.37.053.63-.08.736-.42.184-.133.316-.238.447-.318.578-.316 1.13-.632 1.71-.948.21 0 .316 0 .368-.027.344-.16.66-.342.975-.527a2.258 2.258 0 0 1-.263-.13c.262-.054.34-.08.5-.133.63-.74 1.5-1.24 2.157-1.82.29-.026.29-.105.29-.157.104-.132.21-.29.34-.396.58-.527 1.21-.975 1.737-1.528a37.16 37.16 0 0 0 2.184-2.374c.63-.738 1.264-1.475 1.79-2.292.737-1.133 1.368-2.293 2.026-3.48.474-.842.895-1.685 1.37-2.528.05-.08.157-.185.236-.185.71-.08 1.422-.13 2.106-.21.158-.026.342-.13.5-.21-.08-.132-.132-.29-.21-.422-.106-.16-.264-.29-.37-.45-.104-.13-.183-.29-.262-.447-.08-.13-.158-.236-.237-.37a9.7 9.7 0 0 1-.45-.894c-.026-.08-.08-.21-.052-.29.474-1.027.658-2.134 1.105-3.162.447-1.054.58-2.24.79-3.373.184-1.08.29-2.16.42-3.24.08-.764.185-1.502.21-2.266.16-1.212.106-2.346.08-3.48-.026-1-.08-2.028-.13-3.03zM12.685 66.405c-.184-.21-.342-.448-.526-.658l.08-.08c.287.317.577.633.866.976-.158-.08-.342-.132-.42-.238zm.42.238c.08-.027.16-.027.238-.053.08.132.132.29.21.448-.368-.027-.552-.185-.447-.395zm27.37 10.883v-.08c.5-.052.973-.105 1.473-.157v.077c-.5.08-.973.13-1.473.158zm6.63-.685c-.367.08-.762.133-1.13.186-.132.026-.29.158-.342-.08-.053.027-.106.027-.158.054.13.394.447.078.71.236-.58.08-1.13.132-1.684.21v-.052c.16-.026.343-.053.5-.08v-.078a7.743 7.743 0 0 0-.79-.053c-.077 0-.183.106-.262.132-.105.026-.21.053-.342.053-.447.026-.894.026-1.316.052-.027 0-.08-.026-.106-.026v-.08c1.763-.236 3.5-.473 5.263-.71.027.052.027.105.053.157-.158 0-.263.055-.395.08zm.396-.262c.606-.08 1.16-.132 1.738-.21-1.21.342-1.605.394-1.737.21zM24.58 23.374c.84-1.16 1.71-2.32 2.552-3.505.263-.345.473-.714.736-1.056.08-.106.185-.158.316-.264l-.026-.05c.105-.133.21-.24.263-.344.134-.21.213-.448.318-.685a.385.385 0 0 1 .105-.103c.37.184.37-.21.5-.343.237-.264.474-.553.684-.817.158-.21.316-.395.448-.632.026-.08-.053-.21-.08-.317h-.078c.08-.052.158-.13.237-.184.026 0 .026 0 .052-.026.158-.238.316-.475.474-.686.315-.42.657-.842 1.025-1.21-.052.13-.105.263-.158.368.027 0 .027.027.053.027.316-.422.658-.817.974-1.24-.027-.025-.053-.052-.08-.052-.13.132-.236.264-.368.396-.026-.027-.052-.053-.08-.053.265-.343.528-.685.79-1.08.053.08.106.184.21.395.107-.263.212-.447.29-.632-.078.08-.183.158-.262.238l-.08-.08.474-.71c.5-.712 1-1.45 1.5-2.162.185-.263.42-.474.58-.738.5-1 1.29-1.792 1.894-2.714.132-.184.316-.342.474-.5.13-.16.237-.106.342.026.71.896 1.42 1.818 2.13 2.714.528.66 1.054 1.29 1.554 1.976.605.844 1.184 1.687 1.79 2.53.684.975 1.368 1.95 2.026 2.95 1 1.477 1.947 2.953 2.947 4.428.737 1.08 1.474 2.135 2.184 3.215h-1.344c-1.236-.025-2.5-.13-3.736-.078-1.684.08-3.394.264-5.078.396-2.132.185-4.29.21-6.42.21-.765 0-1.528.107-2.29.16-.922.052-1.817.105-2.738.13-1.08.054-2.13.08-3.21.107-.606.026-1.237 0-1.895 0zm30.183 12.12v.238c-.026 0-.052.027-.105.027-.105-.37-.21-.766-.342-1.135-.263-.765-.553-1.53-1.027-2.214-.528-.737-1-1.5-1.528-2.265-.13-.185-.316-.343-.474-.5-.553-.607-1.106-1.24-1.816-1.687a21.485 21.485 0 0 0-3.29-1.688 7.374 7.374 0 0 1-.92-.474h.63l4.5-.08c.974-.025 1.922-.025 2.895-.078.236 0 .368.08.5.29.236.395.473.79.736 1.186.027.052.08.13.08.21 0 .58 0 1.186.026 1.766.025.606.08 1.186.104 1.792 0 .606-.053 1.238-.026 1.87.027.897.053 1.82.053 2.74zM26.447 26.67c1.237-.053 2.42-.132 3.632-.185.945-.053 1.92-.08 2.866-.132.395-.025.764-.05 1.158 0-.42.212-.842.423-1.21.686-.474.316-.92.737-1.395 1.08-.475.342-.896.764-1.29 1.212-.5.605-1.053 1.132-1.58 1.712-.37.422-.79.817-1.105 1.265-.447.58-.842 1.21-1.263 1.87.132-2.504.29-4.98.184-7.51zm17.185 25.35c-.843.21-1.71.448-2.58.553-.736.106-1.5.08-2.263.08a25.42 25.42 0 0 1-2.028-.08c-.763-.078-1.526-.157-2.263-.5-.633-.29-1.29-.553-1.92-.87-.634-.316-1.265-.684-1.74-1.264-.34-.423-.815-.765-1.236-1.134.08.316.263.58.553.764-.132.158-.316.08-.58-.343-.078.053-.157.08-.21.106.08-.185.158-.37.237-.527-.105-.21-.237-.448-.342-.66-.21-.342-.42-.71-.605-1.053-.053-.08-.053-.158-.105-.237a5.893 5.893 0 0 1-.37-.475c-.21-.315-.394-.657-.657-.974 0 .08.027.158.027.264-.027 0-.053.026-.053.026l-.554-1.344c-.026 0-.026 0-.052.026l.473 1.74c-.026 0-.052.025-.08.025-.077-.104-.156-.21-.21-.34-.052-.212-.21-.212-.34-.133-.08.053-.133.237-.106.316.185.448.395.896.606 1.344.052.158.105.29.184.448.027.053.106.105.106.184.106.21.185.42.316.606.237.316.5.632.737.948.235.316.445.66.656.975.026.053.105.053.13.08.133.395.58.684.896.526.08.606.737.817 1 1.397a11.957 11.957 0 0 1-.763-.343c-.027.026-.027.052-.054.105.316.158.632.316.92.5.265.16.528.317.765.5.316.29.685.45 1.13.554a.282.282 0 0 0-.05-.107c.736.343 1.5.712 2.078 1-2.737.054-5.658.107-8.685.16 0-.5-.026-.975-.026-1.476 0-.21.052-.395.025-.606-.08-1.21-.08-2.424-.237-3.61-.157-1.264-.157-2.503-.13-3.77.025-.683-.027-1.394-.054-2.08 0-.922 0-1.82.028-2.74 0-.132.053-.237.106-.37h.08c.025.054 0 .133.05.16.08.08.212.21.265.184.157-.106.394-.21.447-.37.13-.315.184-.658.184-.974 0-.236.106-.394.21-.553.054-.08.08-.158.133-.263-.105-.08-.21-.132-.342-.237.106-.29.08-.633.475-.79.052-.027.052-.16.08-.238.025-.213.05-.45.078-.66.052.08.08.105.13.157a.42.42 0 0 1 .054-.08c0-.104-.026-.315 0-.315.316-.053.184-.395.342-.553.025-.028-.027-.107-.027-.16 0-.052 0-.13.026-.13.367-.08.315-.475.552-.66.08-.053.105-.13.21-.263.21.368-.158.553-.184.816.446-.263.578-.895.315-1.08.105-.08.21-.184.29-.29.29-.316.604-.606.868-.922.185-.236.29-.526.474-.763.106-.132.316-.237.474-.317.474-.262.92-.552 1.21-1 .053-.053.132-.105.21-.158.08-.053.238-.053.264-.132.027-.052-.052-.184-.105-.263.104-.053.21-.158.42-.264-.08.158-.105.264-.158.37l.13.13c.238-.184.606-.394.843-.552 0-.025-.132-.13-.132-.13-.157.08-.394.21-.63.316.05-.08.05-.132.08-.158.367-.237.735-.474 1.13-.66.92-.42 1.842-.842 2.763-1.237.158-.08.37-.026.553-.026.078 0 .13 0 .21-.026.42-.132.842-.264 1.263-.37.183-.052.393-.078.58-.078.787.025 1.577.025 2.366.078.342.026.658.105.974.21a9.88 9.88 0 0 1 1.184.5c.447.24.868.502 1.29.792.763.5 1.473 1.054 2.236 1.502.737.448 1.316 1.054 1.79 1.74.58.816 1.237 1.554 1.5 2.555l.394 1.74c.08.316.264.632.185 1-.133.66-.238 1.345-.343 2.004-.052.265-.105.53-.078.79.05.82-.265 1.53-.58 2.268-.106.237-.264.475-.395.738a.798.798 0 0 0 .21.106l.237-.474c.027 0 .027 0 .053.027-.132.368-.237.764-.37 1.133-.314.817-.63 1.66-1.025 2.45-.21.448-.58.817-.842 1.24-.262.368-.473.763-.736 1.106-.237.29-.473.58-.79.79-.71.527-1.447 1.054-2.21 1.476-.473.29-1.026.448-1.552.58zm-14.027-1.4l-.026.027c-.055-.026-.134-.052-.186-.105l-.632-.95c-.052-.078-.08-.157-.052-.262.29.448.58.87.895 1.29zm16.37 3.61c1.183-.5 2.157-1.21 3.05-2.028.133-.132.264-.263.422-.37 1.106-.684 1.92-1.633 2.658-2.687.842-1.212 1.395-2.582 2.08-3.873a2.73 2.73 0 0 1 .157-.29c-.053 3.004.29 5.955.684 8.933-2.973.105-6 .21-9.052.316zm26.683-.79c-.026.053-.08.106-.105.16-.027-.054-.027-.133-.053-.24-.158.423-.5.212-.737.212-1.42.027-2.868.027-4.29.027-1.368 0-2.762 0-4.13.024-.448 0-.922.105-1.37.132-1.078.052-2.157.08-3.236.105-.08 0-.158-.13-.29-.236a1.81 1.81 0 0 1-.158.237c-.028-.052-.08-.104-.133-.183-.026.08-.053.158-.08.21H58c-.053-.368-.158-.71-.158-1.08 0-.79.08-1.58.105-2.372.027-.368 0-.71 0-1.054.106.08.185.133.29.21.052-.103.105-.182.158-.26 0 0-.053-.028-.106-.08.05-.027.104-.08.104-.106.026-.08.08-.158.08-.21 0-.185-.054-.343-.08-.5.026 0 .052 0 .08-.028l.157.79h.08c-.106-.183.236-.342-.053-.552-.026-.027.026-.185.026-.264-.08-.157-.13-.315-.21-.526.026-.026.105-.053.184-.08-.105-.052-.184-.104-.263-.13.263-.238.263-.37.026-.633.054-.025.106-.025.106-.05 0-.238 0-.475-.052-.71-.053-.266.08-.58-.316-.74a.79.79 0 0 0 .105.21s-.08.027-.158.08c-.342-.317-.13-.74-.21-1.213.184.053.316.106.447.16-.053-.186-.184-.397-.263-.634h-.107v-1.74c0 .027.184.027.29.054 0-.027.025-.053.025-.08-.08-.105-.185-.21-.29-.342l.053-.053c-.21-.262-.105-.63-.105-.71V39.4c.264.264-.13.606.264.764v-.263h-.027c-.026-.395-.026-.79-.052-1.186h-.052c-.027.054-.027.08-.054.133h-.052l.158-6.298c.263.342.552.66.736 1 .606 1.108 1.395 2.057 2.132 3.058.632.87 1.21 1.818 1.79 2.714.71 1.08 1.394 2.16 2.105 3.24a81.41 81.41 0 0 0 1.63 2.426c.5.71 1.028 1.396 1.554 2.082.446.606.92 1.212 1.367 1.818.527.738 1.053 1.475 1.58 2.187.262.368.552.737.84 1.106.16.21.396.37.554.5-.025 0-.052 0-.104-.026.08.105.13.184.184.237.29.158.316.316.158.554zM74 46.854v-.185c0 .052.026.13 0 .184zm.895-11.62c-.027 0-.184-.16-.21-.186-.027.08 0 .158-.053.264-.027-.078-.21-.052-.21-.13-.027.368.157.737.13 1.106.08-.053.395-.08.474-.158.027.026.08.052.106.052-.527.396-.395.79-.158 1.24.052.104.21.315.052.526-.052.053.027.21.053.343h.077v.05l-.237.08c-.052-.08-.367-.236-.367-.37v1.346c.263.08.263.448.368.633a.768.768 0 0 0 .107-.21l.027.024c-.027.158-.053.316-.106.475-.052.236-.105.447-.13.684 0 .026.05.08.05.105-.288.66-.13 1.396-.235 2.08-.08.5 0 1.03-.053 1.556-.054.448-.16.922-.264 1.37-.027.08-.08.105-.21.158.052-.316.026-.527-.027-.817-.028 0-.37-.184-.397-.184 0 .37.21.87.29 1.29-.08-.026-.395-.21-.42-.21-.054.316-.054.738-.08 1.08-.027.264-.263.5-.29.79 0 .16.184.264.158.528h.21c0-.526.238-1 .238-1.554h.078c.027.053.106.106.08.132-.053.29-.16.606-.132.896 0 .158.13.316.08.5-.054.16-.08.317-.107.554-.027-.132-.053-.184-.053-.263-.026 0-.263-.027-.29-.027-.026.158.185.316.158.448-.026.026-.052.026-.105.053l-.868-1.266c-.686-1-1.37-2.003-2.054-3.03a6.312 6.312 0 0 1-.475-.79 37.09 37.09 0 0 0-2.71-4.033c-.762-.974-1.37-2.03-2.08-3.055-.656-.975-1.314-1.924-1.972-2.9-.237-.315-.526-.605-.737-.948-.683-1.08-1.29-2.187-1.972-3.267-.58-.897-1.21-1.767-1.816-2.636-.21-.29-.42-.607-.632-.923a.37.37 0 0 1-.052-.182c-.053-.58-.106-1.16-.132-1.713 0-.527.053-1.054.053-1.608v-.474c0-.132.025-.237.025-.37.025-.025.052-.078.078-.104-.763 0-1.553-.028-2.316 0-.5.025-.763-.186-1.105-.555-1-1.133-1.737-2.424-2.605-3.636a162.42 162.42 0 0 0-2.5-3.427c-.685-.922-1.37-1.818-2.053-2.74-.764-1.054-1.5-2.108-2.29-3.162a381.983 381.983 0 0 0-2.895-3.794c-.45-.58-.95-1.133-1.45-1.74.343.054.66.106.975.133l1.264.08c.947.077 1.894.13 2.84.26.79.107 1.58.265 2.396.396 1.738.29 3.448.765 5.106 1.318.974.316 1.92.738 2.87 1.133 2.13.87 4.157 1.924 6.157 3.03.63.343 1 .896 1.472 1.397.685.712 1.37 1.423 2.027 2.16.762.87 1.472 1.766 2.21 2.662.657.79 1.34 1.58 2 2.372.21.237.37.527.552.79.42.633.895 1.24 1.263 1.924.262.502.42 1.082.604 1.635.262.817.526 1.607.79 2.424.183.606.34 1.24.472 1.87.106.423.08.87.21 1.29.16.556 0 1.16.16 1.715.025.053.05.132.078.185.105.104.184.21.026.368-.025.026-.025.13 0 .21.054-.052.08-.105.133-.184 0 .053.025.08.025.105 0 .104-.027.21 0 .315 0 .052.052.13.078.184.053-.054.105-.08.21-.16.237.897.264 1.793.264 2.715 0 .87.157 1.74-.21 2.583.078-.29-.106-.555-.027-.818z"/><path d="M58.08 45.482c.025 0 .052.027.052.027l-.027-.03c0-.025 0-.025-.026 0zm4.157 26.036c-.29.21-.58.395-.948.474-.028-.026-.028-.053-.054-.08.29-.184.605-.368.895-.553.027.05.08.104.106.157zM12.895 35.81c.29-.367.58-.736.894-1.105.025.026.235.08.262.105-.29.37-.685.87-.974 1.265-.054-.053-.133-.237-.185-.264zM5.42 48.725c-.21-.448-.42-.923-.63-1.37a.91.91 0 0 1 .236-.106c.29.42.42.92.632 1.37 0 0-.21.105-.237.105zm6.712-12.65c-.158.238-.316.502-.474.74-.026-.028-.316.104-.342.078.158-.237.552-.66.71-.896.027.026.053.053.106.08zM59.422 72.6c.025 0 .025-.026.052-.026.184.026.394.052.605.052-.344.237-.555.21-.66-.026zm-47.24-35.418c.028-.08.08-.158.133-.237.052 0 .13-.027.13-.027.107-.184.107-.316.212-.474-.026-.026-.053-.026-.08-.053-.157.108-.315.24-.473.345.053.052.053.08.053.132-.21-.027-.29.08-.395.368-.026.08-.158.106-.29.21-.026.054-.052.186-.105.317l.027.028c-.053.053-.132.08-.132.08-.158.157-.342.29-.5.447-.026.08-.052.158-.052.237.185-.184.5-.527.737-.738l.027.027c.105-.158.184-.316.29-.474.025.026.025.052.052.08-.08.21-.158.446-.237.657-.055.026-.134.08-.134.053-.105.08-.184.184-.29.263l-.473.316c-.263.237-.526.447-.816.685-.184.29-.368.553-.58.896.317-.08.396.053.37.317.368.052.395-.237.5-.448.026-.054.053-.16.105-.186.237-.21.5-.394.763-.605.053-.053.053-.16.053-.238 0-.026-.133-.026-.212-.053.237-.264.58-.71.816-1 .132-.08.263-.186.263-.265-.026-.29.158-.368.37-.474-.106-.08-.133-.157-.133-.183z"/><path d="M12.71 36.892c-.105.184-.21.342-.315.527l-.158-.08c-.105.605-.474 1.132-.842 1.237.105.053.21.106.29.08.078-.027.13-.16.183-.238l.71-1.028.238-.396-.105-.105zM3.948 48.46c.132 0 .264.026.42.026 0-.105.133-.08.133-.184h.08c0 .132.026.237.026.37h-.552c-.027-.027-.132-.186-.106-.212zm-.21-1.212c-.08-.08-.21-.158-.21-.237-.027-.104.052-.235.13-.367.054.184.08.342.132.527-.027.025-.053.052-.053.078zm.658-1.687c.105.266.21.556.316.82a.798.798 0 0 0-.21.105c-.105-.264-.237-.554-.342-.817a.652.652 0 0 1 .237-.106zm58.58 25.194c.13-.052.288-.08.5-.13-.238.183-.422.315-.58.473-.027-.026-.053-.053-.08-.053.053-.105.106-.184.16-.29zM30.63 15.074c.157-.106.29-.185.447-.29l.052.052c-.16.21-.29.42-.475.685-.026-.183-.026-.29-.053-.42-.026 0 0 0 .027-.026zm7.71 13.333c.237-.106.474-.21.763-.343-.026.158-.026.264-.026.37a.927.927 0 0 0-.264-.054c-.158.027-.448.238-.58.264-.025 0 .106-.21.106-.237zm19.74 22.346c.052.263.552.395.052.658.08.055.157.08.236.134a.2.2 0 0 1-.052.106c-.053.025-.158.078-.21.05-.027 0-.08-.104-.08-.157 0-.237.027-.474.053-.79z"/></g></symbol><symbol viewBox="0 0 24 24" id="powerpoint" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3H8m5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2h2z" fill="#d14524"/></symbol><symbol viewBox="0 0 67.47 70" id="powershell" xmlns="http://www.w3.org/2000/svg"><path d="M18.545 12.4c-3.014 0-6.08 2.34-6.873 5.248L1.91 53.438c-.793 2.908.996 5.248 4.01 5.248h42.887c3.014 0 6.08-2.34 6.873-5.248l9.761-35.79c.794-2.908-.993-5.248-4.007-5.248h-42.89zm4.848 6.243c.652.04 1.29.33 1.76.86l7.96 9.013-3.957 3.246 3.957-3.244 4.832 5.47c.037.042.06.088.094.131.026.034.057.06.082.096.02.028.032.057.05.086.057.087.105.176.15.267.028.06.055.117.08.178a2.546 2.546 0 0 1 .171.764c.005.073.01.146.008.219-.002.09-.01.178-.021.267a2.53 2.53 0 0 1-.036.217 2.56 2.56 0 0 1-.07.252c-.024.076-.048.15-.08.224a2.547 2.547 0 0 1-.111.22 2.503 2.503 0 0 1-.133.218 2.546 2.546 0 0 1-.147.187c-.058.07-.118.137-.185.202-.027.026-.048.057-.076.082-.037.032-.077.054-.116.084-.038.03-.07.065-.11.093L16.8 52.271a2.552 2.552 0 0 1-3.563-.626 2.553 2.553 0 0 1 .63-3.563l18.349-12.853-3.06-3.467-7.839-8.873a2.549 2.549 0 0 1 .225-3.608 2.546 2.546 0 0 1 1.85-.638zm22.441 28.214c1.377 0 2.255 1.083 1.969 2.43-.287 1.347-1.627 2.433-3.004 2.434l-9.957.006c-1.378 0-2.256-1.083-1.969-2.43.287-1.347 1.626-2.433 3.004-2.434l9.957-.006z" fill="#03a9f4" stroke-width="5.342" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 210 210" id="prettier" xmlns="http://www.w3.org/2000/svg"><title>prettier-icon-dark</title><g transform="matrix(.9 0 0 .9 10.5 10.5)" fill="none" fill-rule="evenodd"><rect fill="#56B3B4" x="165" y="40" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="200" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="135" y="120" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="75" y="120" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="120" width="50" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="160" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="80" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="65" y="20" width="110" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="20" width="40" height="10" rx="5"/><rect fill="#F7BA3E" x="55" y="180" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="55" y="60" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="180" width="30" height="10" rx="5"/><rect fill="#F7BA3E" x="15" y="60" width="30" height="10" rx="5"/><rect fill="#56B3B4" x="95" y="100" width="90" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="100" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="100" width="20" height="10" rx="5"/><rect fill="#BF85BF" x="105" y="40" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="40" width="80" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="140" width="100" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="140" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="135" y="60" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="135" y="80" width="60" height="10" rx="5"/><rect fill="#56B3B4" x="15" width="130" height="10" rx="5"/></g></symbol><symbol viewBox="0 0 80 80" id="protractor" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="hxa"><path transform="scale(1 -1)" fill="#564b55" stroke-width="27.224" d="M-2.983-69.251h69.412v67.108H-2.983z"/></clipPath></defs><g transform="matrix(1.13039 0 0 -1.13039 5.714 82.137)" clip-path="url(#hxa)"><g transform="scale(.1)"><path d="M1180.54 92.324c-5.53 0-9.93-1.797-13.23-5.39-3.29-3.614-5.22-8.594-5.81-14.97h36.02c0 6.583-1.47 11.622-4.4 15.126-2.93 3.496-7.12 5.234-12.58 5.234zm2.84-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.88 6.21-8.83 14.824-8.83 25.84 0 11.101 2.73 19.922 8.21 26.464 5.45 6.524 12.81 9.805 22.02 9.805 8.63 0 15.46-2.851 20.48-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.45c.21-8.086 2.26-14.22 6.12-18.418 3.89-4.18 9.34-6.29 16.38-6.29 7.42 0 14.76 1.563 22 4.669V34.14c-3.68-1.602-7.18-2.746-10.48-3.438-3.28-.684-7.24-1.035-11.89-1.035M1272.34 30.918v44.57c0 5.606-1.28 9.805-3.82 12.559-2.56 2.773-6.56 4.16-12.02 4.16-7.2 0-12.49-1.953-15.84-5.851-3.34-3.895-5.03-10.32-5.03-19.286V30.918h-10.42v68.887h8.47l1.71-9.422h.5c2.14 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.15 2.804 12.88 2.804 8.29 0 14.54-2.011 18.73-6.015 4.19-3.985 6.28-10.391 6.28-19.192V30.918h-10.43M1328.96 38.406c7.1 0 12.27 1.938 15.48 5.813 3.22 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.44 6.25-15.56 6.25-6.11 0-10.79-2.383-14.04-7.129-3.26-4.746-4.88-11.472-4.88-20.136 0-8.797 1.61-15.45 4.84-19.93 3.23-4.484 7.97-6.723 14.22-6.723zm20.85 1.762h-.56c-4.83-7.004-12.02-10.5-21.62-10.5-9.01 0-16.03 3.066-21.04 9.238-5 6.153-7.5 14.922-7.5 26.27 0 11.355 2.51 20.176 7.54 26.465 5.03 6.289 12.03 9.433 21 9.433 9.34 0 16.5-3.398 21.49-10.195h.81l-.43 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.38 9.25M1434.91 38.27c1.85 0 3.63.136 5.34.421 1.72.274 3.09.547 4.1.84v-7.976c-1.15-.559-2.81-.996-5.01-1.36-2.18-.351-4.17-.527-5.94-.527-13.32 0-19.97 7.012-19.97 21.055V91.71h-9.88v5.027l9.88 4.336 4.38 14.707h6.04V99.805h20V91.71h-20V51.16c0-4.15.98-7.333 2.96-9.56 1.97-2.206 4.67-3.331 8.1-3.331M1463.81 65.43c0-8.809 1.76-15.508 5.27-20.118 3.53-4.609 8.69-6.906 15.53-6.906s12.01 2.297 15.56 6.875c3.53 4.602 5.3 11.301 5.3 20.149 0 8.75-1.77 15.41-5.3 19.953-3.55 4.539-8.77 6.824-15.69 6.824-6.82 0-11.99-2.246-15.47-6.73-3.46-4.48-5.2-11.16-5.2-20.047zm52.47 0c0-11.23-2.83-20-8.48-26.309-5.66-6.309-13.47-9.453-23.44-9.453-6.17 0-11.64 1.445-16.42 4.336-4.78 2.89-8.46 7.031-11.06 12.45-2.59 5.401-3.88 11.73-3.88 18.976 0 11.23 2.8 19.968 8.41 26.242 5.61 6.258 13.4 9.402 23.38 9.402 9.64 0 17.3-3.222 22.97-9.62 5.69-6.415 8.52-15.087 8.52-26.024M1591.71 92.324c-5.54 0-9.94-1.797-13.23-5.39-3.3-3.614-5.24-8.594-5.81-14.97h36c0 6.583-1.46 11.622-4.39 15.126-2.93 3.496-7.13 5.234-12.57 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.89 6.21-8.83 14.824-8.83 25.84 0 11.101 2.74 19.922 8.2 26.464 5.46 6.524 12.81 9.805 22.04 9.805 8.62 0 15.45-2.851 20.48-8.523 5.03-5.676 7.54-13.157 7.54-22.461v-6.613h-47.45c.21-8.086 2.25-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.36-6.29 7.43 0 14.77 1.563 22.01 4.669V34.14c-3.69-1.602-7.17-2.746-10.46-3.438-3.3-.684-7.27-1.035-11.91-1.035M1683.5 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12.01 4.16-7.2 0-12.48-1.953-15.83-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M1740.11 38.406c7.12 0 12.28 1.938 15.49 5.813 3.21 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.43 6.25-15.56 6.25-6.12 0-10.8-2.383-14.05-7.129-3.24-4.746-4.88-11.472-4.88-20.136 0-8.797 1.64-15.45 4.85-19.93 3.22-4.484 7.96-6.723 14.21-6.723zm20.87 1.762h-.57c-4.82-7.004-12.03-10.5-21.62-10.5-9.01 0-16.02 3.066-21.03 9.238-5 6.153-7.52 14.922-7.52 26.27 0 11.355 2.52 20.176 7.55 26.465 5.02 6.289 12.02 9.433 21 9.433 9.34 0 16.5-3.398 21.48-10.195h.83l-.44 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.37 9.25M1846.07 38.27c1.85 0 3.64.136 5.36.421 1.7.274 3.07.547 4.08.84v-7.976c-1.13-.559-2.8-.996-5-1.36-2.2-.351-4.18-.527-5.94-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.86v5.027l9.86 4.336 4.4 14.707h6.04V99.805H1855V91.71h-19.98V51.16c0-4.15.98-7.333 2.95-9.56 1.97-2.206 4.68-3.331 8.1-3.331M1894.26 92.324c-5.53 0-9.94-1.797-13.22-5.39-3.31-3.614-5.25-8.594-5.83-14.97h36.01c0 6.583-1.45 11.622-4.38 15.126-2.95 3.496-7.13 5.234-12.58 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.1 9.297-5.9 6.21-8.84 14.824-8.84 25.84 0 11.101 2.73 19.922 8.2 26.464 5.47 6.524 12.81 9.805 22.03 9.805 8.63 0 15.46-2.851 20.49-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.46c.22-8.086 2.26-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.37-6.29 7.42 0 14.75 1.563 22 4.669V34.14c-3.7-1.602-7.17-2.746-10.47-3.438-3.28-.684-7.25-1.035-11.9-1.035M1983.36 49.727c0-6.426-2.4-11.368-7.18-14.844-4.77-3.477-11.47-5.215-20.11-5.215-9.13 0-16.26 1.445-21.37 4.336v9.687a51.32 51.32 0 0 1 10.65-3.964c3.79-.977 7.45-1.457 10.97-1.457 5.46 0 9.64.87 12.57 2.609 2.95 1.738 4.41 4.394 4.41 7.95 0 2.694-1.17 4.98-3.5 6.894-2.32 1.914-6.85 4.152-13.6 6.757-6.41 2.383-10.97 4.473-13.67 6.25-2.71 1.778-4.72 3.81-6.04 6.067-1.31 2.254-1.98 4.96-1.98 8.113 0 5.606 2.29 10.04 6.86 13.281 4.57 3.25 10.84 4.883 18.79 4.883 7.42 0 14.66-1.515 21.74-4.531l-3.71-8.496c-6.9 2.851-13.17 4.277-18.79 4.277-4.94 0-8.67-.77-11.18-2.324-2.52-1.543-3.78-3.691-3.78-6.406 0-1.844.48-3.418 1.42-4.707.95-1.309 2.46-2.54 4.56-3.711 2.09-1.184 6.11-2.871 12.07-5.086 8.16-2.98 13.69-5.98 16.55-8.996 2.87-3.02 4.32-6.809 4.32-11.367M2021.28 38.27c1.85 0 3.64.136 5.35.421 1.71.274 3.09.547 4.09.84v-7.976c-1.14-.559-2.81-.996-5.01-1.36-2.18-.351-4.18-.527-5.93-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.87v5.027l9.87 4.336 4.4 14.707h6.02V99.805h20V91.71h-20V51.16c0-4.15 1-7.333 2.97-9.56 1.98-2.206 4.67-3.331 8.1-3.331M2053.61 30.918h-10.42v68.887h10.42zm-11.31 87.559c0 2.39.59 4.14 1.76 5.253 1.18 1.106 2.65 1.661 4.42 1.661 1.67 0 3.1-.567 4.32-1.7 1.22-1.132 1.82-2.871 1.82-5.214 0-2.344-.6-4.09-1.82-5.247-1.22-1.16-2.65-1.726-4.32-1.726-1.77 0-3.24.566-4.42 1.726-1.17 1.157-1.76 2.903-1.76 5.247M2121.59 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.49l1.69-9.422h.5c2.15 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.16 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2159.29 77.742c0-4.812 1.35-8.465 4.08-10.926 2.72-2.48 6.51-3.71 11.37-3.71 10.19 0 15.28 4.953 15.28 14.831 0 10.344-5.16 15.532-15.47 15.532-4.9 0-8.67-1.32-11.31-3.965-2.63-2.649-3.95-6.555-3.95-11.762zm-5.67-58.387c0-3.73 1.58-6.55 4.72-8.488 3.14-1.922 7.65-2.879 13.52-2.879 8.75 0 15.24 1.309 19.45 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.15 6.32-3.45 7.754-2.31 1.457-6.65 2.168-13.01 2.168h-12.51c-4.74 0-8.43-1.12-11.06-3.386-2.65-2.266-3.97-5.508-3.97-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.23-3.39 3.15-5.754.91-2.371 1.37-5.039 1.37-8.02 0-6.746-2.29-12.128-6.91-16.152-4.61-4.012-10.93-6.023-18.98-6.023-2.05 0-3.98.156-5.78.5-4.45-2.356-6.67-5.305-6.67-8.871 0-1.883.77-3.282 2.34-4.176 1.54-.902 4.21-1.36 7.97-1.36h12.2c7.46 0 13.19-1.574 17.19-4.707 4-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2192.38 2.004 2183.46 0 2171.72 0c-9 0-15.95 1.68-20.82 5.027-4.88 3.352-7.34 8.079-7.34 14.211 0 4.18 1.35 7.813 4.03 10.88 2.68 3.046 6.45 5.116 11.32 6.21-1.77.8-3.24 2.031-4.44 3.711-1.19 1.68-1.78 3.633-1.78 5.84 0 2.52.66 4.707 2.01 6.602 1.34 1.882 3.44 3.71 6.34 5.468-3.56 1.465-6.46 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.26 13.37 6.79 17.452 4.52 4.082 10.93 6.133 19.22 6.133 3.6 0 6.86-.429 9.75-1.27h23.82M2284.61 91.71h-17.54V30.919h-10.43v60.793h-12.31v4.707l12.31 3.766v3.839c0 16.922 7.4 25.391 22.19 25.391 3.65 0 7.93-.73 12.82-2.195l-2.7-8.364c-4.03 1.301-7.46 1.946-10.31 1.946-3.93 0-6.85-1.309-8.73-3.926-1.89-2.617-2.84-6.816-2.84-12.598v-4.472h17.54V91.71M2302.87 65.43c0-8.809 1.76-15.508 5.28-20.118 3.52-4.609 8.7-6.906 15.52-6.906 6.84 0 12.02 2.297 15.57 6.875 3.54 4.602 5.3 11.301 5.3 20.149 0 8.75-1.76 15.41-5.3 19.953-3.55 4.539-8.78 6.824-15.69 6.824-6.83 0-11.99-2.246-15.46-6.73-3.48-4.48-5.22-11.16-5.22-20.047zm52.48 0c0-11.23-2.82-20-8.47-26.309-5.67-6.309-13.48-9.453-23.46-9.453-6.15 0-11.62 1.445-16.4 4.336-4.77 2.89-8.47 7.031-11.06 12.45-2.59 5.401-3.9 11.73-3.9 18.976 0 11.23 2.81 19.968 8.43 26.242 5.6 6.258 13.4 9.402 23.38 9.402 9.63 0 17.28-3.222 22.97-9.62 5.68-6.415 8.51-15.087 8.51-26.024M2403.79 101.074c3.07 0 5.8-.254 8.22-.761l-1.43-9.676c-2.86.633-5.37.933-7.55.933-5.58 0-10.33-2.261-14.3-6.785-3.95-4.531-5.94-10.156-5.94-16.902V30.918h-10.43v68.887h8.62l1.19-12.754h.5c2.56 4.48 5.63 7.949 9.23 10.37 3.61 2.423 7.56 3.653 11.89 3.653M2500.33 69.766l-10.68 28.476c-1.39 3.594-2.81 8.028-4.28 13.262-.93-4.024-2.24-8.438-3.96-13.262l-10.81-28.476zm14.77-38.848l-11.44 29.227h-36.83l-11.32-29.227h-10.81l36.34 92.273h8.98l36.13-92.273h-11.05M2583.07 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2620.76 77.742c0-4.812 1.36-8.465 4.08-10.926 2.73-2.48 6.53-3.71 11.37-3.71 10.2 0 15.28 4.953 15.28 14.831 0 10.344-5.15 15.532-15.45 15.532-4.91 0-8.68-1.32-11.32-3.965-2.64-2.649-3.96-6.555-3.96-11.762zm-5.66-58.387c0-3.73 1.57-6.55 4.71-8.488 3.15-1.922 7.65-2.879 13.53-2.879 8.75 0 15.23 1.309 19.44 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.14 6.32-3.45 7.754-2.31 1.457-6.64 2.168-13 2.168h-12.51c-4.74 0-8.43-1.12-11.07-3.386-2.63-2.266-3.96-5.508-3.96-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.22-3.39 3.14-5.754.92-2.371 1.38-5.039 1.38-8.02 0-6.746-2.3-12.128-6.92-16.152-4.61-4.012-10.92-6.023-18.97-6.023-2.05 0-3.99.156-5.78.5-4.46-2.356-6.67-5.305-6.67-8.871 0-1.883.78-3.282 2.33-4.176 1.55-.902 4.21-1.36 7.98-1.36h12.2c7.46 0 13.18-1.574 17.18-4.707 4.01-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2653.87 2.004 2644.94 0 2633.2 0c-9 0-15.95 1.68-20.83 5.027-4.88 3.352-7.33 8.079-7.33 14.211 0 4.18 1.35 7.813 4.02 10.88 2.69 3.046 6.47 5.116 11.32 6.21-1.77.8-3.23 2.031-4.43 3.711-1.19 1.68-1.79 3.633-1.79 5.84 0 2.52.66 4.707 2.01 6.602 1.35 1.882 3.45 3.71 6.35 5.468-3.56 1.465-6.47 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.25 13.37 6.79 17.452 4.52 4.082 10.92 6.133 19.21 6.133 3.62 0 6.86-.429 9.75-1.27h23.83M2692.7 99.805V55.117c0-5.605 1.27-9.805 3.83-12.566 2.56-2.766 6.57-4.145 12.01-4.145 7.2 0 12.47 1.965 15.81 5.903 3.33 3.945 4.99 10.379 4.99 19.304v36.192h10.44V30.918h-8.62l-1.5 9.25h-.58c-2.13-3.41-5.1-5.988-8.88-7.793-3.8-1.809-8.13-2.707-12.99-2.707-8.37 0-14.65 1.992-18.81 5.977-4.18 3.964-6.26 10.351-6.26 19.101v45.059h10.56M2760.61 30.918h10.43v97.805h-10.43zM2810.67 38.27c6.5 0 11.6 1.789 15.31 5.343 3.71 3.575 5.56 8.555 5.56 14.961v6.23l-10.44-.448c-8.3-.286-14.27-1.583-17.94-3.868-3.66-2.273-5.5-5.82-5.5-10.644 0-3.781 1.14-6.64 3.42-8.613 2.29-1.973 5.48-2.961 9.59-2.961zm23.57-7.352l-2.07 9.805h-.51c-3.44-4.305-6.86-7.227-10.27-8.77-3.42-1.523-7.68-2.285-12.8-2.285-6.83 0-12.17 1.758-16.05 5.273-3.87 3.528-5.81 8.536-5.81 15.032 0 13.906 11.12 21.199 33.37 21.875l11.7.359v4.277c0 5.418-1.17 9.395-3.5 11.985-2.32 2.566-6.03 3.855-11.15 3.855-5.74 0-12.24-1.758-19.49-5.273l-3.21 7.988c3.4 1.836 7.11 3.281 11.16 4.324a47.81 47.81 0 0 0 12.16 1.575c8.23 0 14.3-1.817 18.27-5.461 3.96-3.66 5.93-9.5 5.93-17.54V30.919h-7.73M2893.6 101.074c3.07 0 5.8-.254 8.25-.761l-1.46-9.676c-2.84.633-5.35.933-7.54.933-5.56 0-10.33-2.261-14.3-6.785-3.96-4.531-5.93-10.156-5.93-16.902V30.918h-10.44v68.887h8.61l1.19-12.754h.5c2.57 4.48 5.65 7.949 9.25 10.37 3.6 2.423 7.56 3.653 11.87 3.653M2901.63 6.727c-3.94 0-7.04.558-9.31 1.691v9.121c2.97-.84 6.08-1.25 9.31-1.25 4.14 0 7.3 1.25 9.45 3.77 2.16 2.507 3.24 6.132 3.24 10.859v91.895h10.69V31.797c0-7.95-2.01-14.121-6.04-18.496-4.02-4.383-9.8-6.574-17.34-6.574M2999.96 55.371c0-8.086-2.93-14.394-8.8-18.918-5.87-4.52-13.83-6.785-23.88-6.785-10.9 0-19.27 1.406-25.14 4.219v10.3c3.77-1.59 7.88-2.847 12.31-3.765 4.45-.93 8.85-1.399 13.21-1.399 7.12 0 12.49 1.36 16.09 4.063 3.59 2.695 5.4 6.465 5.4 11.277 0 3.196-.63 5.805-1.91 7.832-1.29 2.024-3.42 3.907-6.42 5.625-2.99 1.711-7.56 3.664-13.67 5.84-8.55 3.059-14.66 6.692-18.32 10.871-3.66 4.2-5.51 9.668-5.51 16.407 0 7.089 2.68 12.714 7.99 16.914 5.32 4.191 12.36 6.289 21.12 6.289 9.13 0 17.54-1.68 25.2-5.032l-3.32-9.304c-7.59 3.183-14.96 4.785-22.13 4.785-5.66 0-10.07-1.223-13.26-3.652-3.19-2.43-4.78-5.809-4.78-10.118 0-3.191.59-5.8 1.76-7.832 1.17-2.031 3.14-3.886 5.95-5.597 2.78-1.688 7.04-3.563 12.79-5.625 9.63-3.426 16.26-7.118 19.89-11.063 3.62-3.937 5.43-9.043 5.43-15.332M741.648 375.406h30c28.965 0 50.227 5.039 63.774 15.117 13.531 10.079 20.32 25.821 20.32 47.247 0 19.832-6.074 34.628-18.191 44.402-12.141 9.758-31.028 14.641-56.692 14.641h-39.211zm172.192 64.246c0-36.062-11.809-63.691-35.434-82.898-23.621-19.219-57.234-28.82-100.847-28.82h-35.911V198.73h-56.445v345.329h99.438c43.14 0 75.457-8.829 96.961-26.465 21.496-17.637 32.238-43.614 32.238-77.942M1099.26 464.691c11.17 0 20.39-.789 27.63-2.371l-5.43-51.718c-7.88 1.894-16.07 2.832-24.57 2.832-22.2 0-40.19-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.514v261.227h43.464l7.32-46.055h2.83c8.66 15.594 19.96 27.95 33.9 37.09 13.93 9.141 28.93 13.699 45 13.699M1206.88 329.82c0-60.308 22.28-90.465 66.85-90.465 44.08 0 66.13 30.157 66.13 90.465 0 59.688-22.21 89.512-66.61 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.95-75.972-32.83-99.898-21.89-23.945-52.35-35.918-91.41-35.918-24.41 0-45.97 5.508-64.7 16.543-18.75 11.016-33.16 26.836-43.23 47.48-10.08 20.625-15.11 44.551-15.11 71.793 0 42.364 10.86 75.43 32.58 99.2 21.73 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.05-24.328 33.06-57.121 33.06-98.379M1558.11 238.887c13.54 0 27.07 2.129 40.62 6.386v-41.816c-6.13-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.59 0-78.88 27.715-78.88 83.144v140.778h-35.68v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.48-9.57 26.34-9.57M1783.44 464.691c11.17 0 20.38-.789 27.62-2.371l-5.43-51.718c-7.88 1.894-16.06 2.832-24.56 2.832-22.2 0-40.2-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.52v261.227h43.46l7.34-46.055h2.82c8.66 15.594 19.95 27.95 33.9 37.09 13.92 9.141 28.93 13.699 45 13.699M1925.05 236.523c20.15 0 36.32 5.625 48.52 16.895 12.21 11.25 18.31 27.051 18.31 47.344v22.676l-33.54-1.407c-26.13-.937-45.16-5.312-57.04-13.105-11.89-7.793-17.82-19.727-17.82-35.781 0-11.661 3.45-20.665 10.39-27.051 6.91-6.387 17.32-9.571 31.18-9.571zm82.66-37.793l-11.11 36.387h-1.87c-12.62-15.918-25.29-26.738-38.04-32.48-12.74-5.742-29.13-8.633-49.13-8.633-25.67 0-45.7 6.934-60.1 20.801-14.41 13.847-21.62 33.457-21.62 58.808 0 26.934 10 47.246 30 60.934 19.99 13.691 50.45 21.172 91.41 22.441l45.09 1.414v13.938c0 16.699-3.88 29.16-11.68 37.441-7.79 8.262-19.88 12.383-36.25 12.383-13.39 0-26.23-1.953-38.5-5.891a294.638 294.638 0 0 1-35.44-13.933l-17.94 39.668c14.17 7.41 29.68 13.035 46.52 16.894 16.85 3.868 32.77 5.789 47.72 5.789 33.22 0 58.31-7.246 75.22-21.726 16.94-14.492 25.4-37.246 25.4-68.262V198.73h-39.68M2220.04 194.004c-39.52 0-69.55 11.543-90.1 34.609-20.55 23.067-30.82 56.172-30.82 99.321 0 43.925 10.74 77.707 32.23 101.339 21.5 23.614 52.56 35.418 93.18 35.418 27.56 0 52.35-5.117 74.41-15.359l-16.78-44.641c-23.46 9.133-42.82 13.704-58.1 13.704-45.19 0-67.79-29.993-67.79-89.981 0-29.293 5.63-51.305 16.89-66.031 11.26-14.707 27.76-22.09 49.48-22.09 24.72 0 48.11 6.152 70.15 18.437v-48.417c-9.92-5.84-20.5-10-31.76-12.52-11.26-2.52-24.93-3.789-40.99-3.789M2451.52 238.887c13.54 0 27.08 2.129 40.63 6.386v-41.816c-6.15-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.6 0-78.9 27.715-78.9 83.144v140.778h-35.66v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.47-9.57 26.33-9.57M2585.92 329.82c0-60.308 22.28-90.465 66.84-90.465 44.09 0 66.15 30.157 66.15 90.465 0 59.688-22.22 89.512-66.62 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.94-75.972-32.83-99.898-21.89-23.945-52.36-35.918-91.4-35.918-24.42 0-45.98 5.508-64.72 16.543-18.74 11.016-33.14 26.836-43.22 47.48-10.07 20.625-15.12 44.551-15.12 71.793 0 42.364 10.87 75.43 32.59 99.2 21.74 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.04-24.328 33.06-57.121 33.06-98.379M2972.33 464.691c11.18 0 20.38-.789 27.63-2.371l-5.43-51.718c-7.87 1.894-16.05 2.832-24.57 2.832-22.2 0-40.19-7.246-53.96-21.731-13.78-14.48-20.67-33.301-20.67-56.453V198.73h-55.51v261.227h43.46l7.33-46.055h2.83c8.66 15.594 19.96 27.95 33.89 37.09 13.94 9.141 28.94 13.699 45 13.699" fill="#100f0d"/><path d="M610.11 372.83c0-170.584-138.257-308.862-308.846-308.862-170.602 0-308.846 138.278-308.846 308.863 0 170.576 138.244 308.846 308.846 308.846 170.59 0 308.846-138.27 308.846-308.846" fill="#e53935" stroke-width="1.029"/><path d="M460.694 521.792l-105.04.958-61.415 61.415-72.096-47.883 12.445-12.438-29.207.26-99.129-166.817H67.357l24.39-24.402-24.57-41.363L294.66 64.049c2.192-.04 4.399-.08 6.603-.08 170.416 0 308.585 138.055 308.846 308.408L460.694 521.792" fill="#d51c2f" stroke-width="1.029"/><path d="M149.093 350.258c0 84.048 68.13 152.151 152.171 152.151 84.028 0 152.139-68.103 152.139-152.151zm342.063-7.017v14.046h44.015c-1.75 59.337-25.556 113.104-63.54 153.419L438.75 477.81l-9.925 9.94 32.875 32.887c-40.314 37.983-94.081 61.79-153.41 63.527l-.015-44.003h-14.035v44.003c-59.34-1.737-113.096-25.556-153.41-63.527l32.887-32.887-9.945-9.92-32.883 32.875c-37.975-40.315-61.781-94.082-63.53-153.419h44.002l-.008-14.034H67.176v-51.511h468.176v51.5h-44.196" fill="#f5f5f5" stroke-width="1.029"/></g></g></symbol><symbol id="pug" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:#c1272d}.hyst1{fill:#efcca3}.st2{fill:#ed1c24}.hyst3{fill:#ccac8d}.hyst4{fill:#fff}.st5{fill:#ff931e}.st6{fill:#ffb81e}.hyst7{fill:#56332b}.hyst8{fill:#442823}.hyst9{fill:#7f4a41}.hyst10{fill:#331712}.st11{fill:#fc6}.st12{fill:#ccc}.st13{fill:#b3b3b3}.st14{fill:#989898}.st15{fill:#323232}.st16{fill:#1e1e1e}.st17{fill:#4c4c4c}.st18{fill:#e6e6e6}.st19{fill:#606060}</style><path class="hyst1" d="M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3z"/><path class="hyst3" d="M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4 2.3 2.5 5.3 5 6.4 8.9 1.1 3.9 1.4 8.9 1.4 10.2 0 1.3.7 1 2.7 0 4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6"/><path class="hyst3" d="M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5z"/><circle cx="92.3" cy="58.1" r="8.8"/><circle class="hyst4" cx="90" cy="54.2" r="2.3"/><path class="hyst1" d="M78.9 57.7s7.9 5.4 12.2 10.7c4.3 5.3 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7l2.6-1.3z"/><path class="hyst3" d="M64.9 54.5c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4-2.3 2.5-5.4 5-6.5 8.9-1.1 3.9-1.5 8.9-1.5 10.2 0 1.3.2 1.4-2.7 0-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5"/><path class="hyst7" d="M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5z"/><path class="hyst8" d="M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6-.5-11z"/><path class="hyst8" d="M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6.5-11z"/><path class="hyst7" d="M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1-1 2.4-4.6 3.1-8.3 3.2-3.6-.1-7.3-.8-8.3-3.2z"/><path d="M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7c0 .3-.1 1.3-.1 2 0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2z"/><path class="hyst9" d="M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6z"/><path class="hyst8" d="M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5z"/><path class="hyst8" d="M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8zM74.8 85s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8z"/><path class="hyst3" d="M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5z"/><path d="M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3-4.4 0-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9z"/><ellipse transform="rotate(-14.465 66.712 61.468)" class="hyst10" cx="66.7" cy="61.5" rx=".8" ry="1.5"/><ellipse transform="rotate(17.235 62.371 61.462)" class="hyst10" cx="62.4" cy="61.5" rx=".8" ry="1.5"/><circle cx="37.2" cy="58.1" r="8.8"/><circle class="hyst4" cx="39.5" cy="54.2" r="2.3"/><path class="hyst9" d="M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1h5.8z"/><path class="hyst1" d="M50 57.7s-7.9 5.4-12.2 10.7c-4.3 5.3-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7L50 57.7z"/><path class="hyst3" d="M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5zM95.8 41.7s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5zM78.7 55.5s-5.9-6.2-13.8-6.4h.1.1c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7zM71.8 42.5s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2zM37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9zM92 73.2s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9z"/><path class="hyst3" d="M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3.9.3 2.1-3.8 10.1-3.4 8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2zM86.9 41.2c.2 0 .3.1.4.1.1 0-.1-.1-.4-.1zM86.9 41.2zM39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3 12.4-5.2zM89.9 28.9s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3-12.4-5.2z"/><path class="hyst7" d="M89.4 28.9s11.6 9.7 15 20.9c3.4 11.2 2 24.8 4.6 26.5 3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6.5-1.1-14.8-17.8-21.5-21.1-8.1-3.8-18.1-4.1-17.4-3.3z"/><path class="hyst8" d="M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1z"/><path class="hyst7" d="M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3-.3 52.2 15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3z"/><path class="hyst8" d="M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1z"/><path class="hyst3" d="M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2zM107.1 74.6s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2z"/><g><circle class="hyst8" cx="54.5" cy="70.5" r=".8"/><circle class="hyst8" cx="49.9" cy="75.3" r=".8"/><circle class="hyst8" cx="48.4" cy="70.5" r=".8"/></g><g><circle class="hyst8" cx="74" cy="70.5" r=".8"/><circle class="hyst8" cx="78.6" cy="75.3" r=".8"/><circle class="hyst8" cx="80.1" cy="70.5" r=".8"/></g></symbol><symbol viewBox="0 0 50 50" id="puppet" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" fill="#fbc02d"><path stroke-width=".283" d="M11.559 249.467h13.587v13.587H11.559zM27.435 265.056h13.587v13.587H27.435zM11.559 281.074h13.587v13.587H11.559z"/><path stroke-width=".256" d="M16.62 251.615l18.305 18.305-3.236 3.236-18.305-18.305z"/><path stroke-width=".256" d="M37.834 271.331L19.53 289.636l-3.237-3.237 18.305-18.304z"/></g></symbol><symbol viewBox="0 0 100 99.999997" id="purescript" xmlns="http://www.w3.org/2000/svg"><path clip-path="url(#SVGID_2_)" d="M98.079 38.548L79.22 19.68l-5.087 5.088L90.447 41.09 74.134 57.41l5.087 5.087 18.858-18.86a3.59 3.59 0 0 0 1.055-2.55 3.578 3.578 0 0 0-1.055-2.54M25.483 42.794l-5.09-5.089L1.53 56.568a3.566 3.566 0 0 0-1.05 2.545c0 .961.373 1.863 1.05 2.542L20.394 80.52l5.089-5.086L9.162 59.113z" fill="#42a5f5" stroke-width="1.192"/><path clip-path="url(#SVGID_2_)" transform="matrix(1.19175 0 0 1.19175 -306.84 -629.047)" fill="#42a5f5" d="M281.841 551.736l6.461 6.037h28.379l-6.461-6.037zM288.302 566.861l-6.463 6.035h28.381l6.463-6.035zM281.838 581.982l6.464 6.035h28.381l-6.463-6.035z"/></symbol><symbol viewBox="0 0 24 24" id="python" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 7.5A2.86 2.86 0 0 1 22 10.36v3.78A2.86 2.86 0 0 1 19.14 17H12c0 .39.32.96.71.96H17v1.68a2.86 2.86 0 0 1-2.86 2.86H9.86A2.86 2.86 0 0 1 7 19.64v-3.75a2.85 2.85 0 0 1 2.86-2.85h5.25a2.85 2.85 0 0 0 2.85-2.86V7.5h1.18m-4.28 11.79c-.4 0-.72.3-.72.89 0 .59.32.71.72.71a.71.71 0 0 0 .71-.71c0-.59-.32-.89-.71-.89m-10-1.79A2.86 2.86 0 0 1 2 14.64v-3.78A2.86 2.86 0 0 1 4.86 8H12c0-.39-.32-.96-.71-.96H7V5.36A2.86 2.86 0 0 1 9.86 2.5h4.28A2.86 2.86 0 0 1 17 5.36v3.75a2.85 2.85 0 0 1-2.86 2.85H8.89a2.85 2.85 0 0 0-2.85 2.86v2.68H4.86M9.14 5.71c.4 0 .72-.3.72-.89 0-.59-.32-.71-.72-.71-.39 0-.71.12-.71.71s.32.89.71.89z"/><path d="M9.264 22.379c-.895-.24-1.581-.799-1.947-1.582-.228-.489-.237-.606-.238-2.957-.001-2.745.057-3.074.666-3.785.193-.226.568-.517.833-.648.47-.23.579-.239 3.839-.288 3.131-.048 3.386-.065 3.814-.264.626-.291 1.07-.687 1.4-1.247.27-.46.278-.522.311-2.29l.034-1.82.932.051c1.075.058 1.504.211 2.098.748.853.77.869.841.869 3.957 0 2.434-.02 2.783-.18 3.075a3.365 3.365 0 0 1-1.337 1.33l-.517.273-3.95.031-3.951.031.068.274c.037.151.164.377.282.503.209.224.262.229 2.433.229h2.22v1.05c0 1.653-.394 2.437-1.54 3.072l-.545.302-2.644.018c-1.455.01-2.782-.018-2.95-.063zm6.12-1.692c.22-.222.253-.325.206-.675-.07-.523-.278-.73-.732-.73-.467 0-.672.217-.735.78-.042.372-.012.496.163.672.3.3.77.28 1.097-.047z" fill="#fc0" stroke="#fc0" stroke-width=".102"/><path d="M9.349 22.38c-.911-.15-1.936-1.074-2.176-1.963-.073-.273-.101-1.279-.079-2.868.033-2.317.047-2.473.27-2.926.13-.263.401-.623.603-.8.674-.592.87-.63 3.484-.675 4.399-.076 4.927-.166 5.705-.967.642-.662.706-.9.774-2.883l.061-1.784.951.055c.523.031 1.11.122 1.304.204.54.225 1.358 1.042 1.472 1.47.153.572.243 3.18.16 4.617-.071 1.23-.093 1.327-.395 1.78-.193.288-.577.647-.966.903l-.647.425-3.922.008c-2.157.004-3.942.028-3.966.052-.115.115.354.82.587.883.14.038 1.181.073 2.314.079l2.06.01v.91c0 1.739-.326 2.446-1.454 3.162l-.631.4-2.543-.011c-1.398-.007-2.733-.043-2.966-.081zm5.98-1.718c.285-.256.313-.328.251-.658-.09-.483-.301-.682-.722-.682-.436 0-.625.193-.715.73-.065.384-.044.453.2.663.358.308.595.295.985-.053z" fill="#fdd835" stroke-width=".102"/><path d="M4.281 17.396c-.88-.215-1.714-.935-2.024-1.747-.149-.389-.168-.804-.142-3.041.027-2.26.054-2.638.215-2.962.259-.519.851-1.092 1.392-1.346.437-.206.632-.217 4.408-.245l3.95-.03-.067-.275a1.367 1.367 0 0 0-.282-.504c-.21-.224-.263-.23-2.433-.23h-2.22l.002-1.143c.003-1.338.157-1.795.84-2.493.746-.763 1.103-.838 4.025-.838 2.961 0 3.28.06 4.067.768.37.333.572.621.728 1.037.201.539.213.735.183 3.072-.035 2.777-.045 2.824-.78 3.598-.787.829-.76.824-4.59.883-3.812.06-3.797.057-4.61.806-.765.706-.917 1.2-.964 3.133l-.04 1.653-.677-.01c-.371-.007-.813-.045-.98-.086zM9.59 5.551c.237-.204.286-.326.286-.72 0-.547-.201-.763-.71-.763-.502 0-.765.248-.765.724 0 .492.141.782.439.902.345.14.444.12.75-.143z" fill="#3c78aa"/></symbol><symbol viewBox="0 0 24 24" id="r" xmlns="http://www.w3.org/2000/svg"><path d="M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1.081 1.081 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z" fill="#1976d2" stroke-width="1.035"/></symbol><symbol viewBox="0 0 24 24" id="raml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="razor" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 11.91c-.11-2.21-1.75-3.54-3.73-3.54h-.08c-2.29 0-3.55 1.8-3.55 3.84 0 2.29 1.53 3.74 3.54 3.74 2.25 0 3.72-1.65 3.83-3.59m-3.81-5.97c1.53 0 2.97.68 4.02 1.74 0-.51.33-.89.83-.89h.11c.74 0 .89.7.89.92v7.9c-.04.52.54.78.87.44 1.27-1.29 2.78-6.69-.79-9.81-3.33-2.92-7.8-2.44-10.18-.8-2.52 1.74-4.14 5.61-2.57 9.22 1.71 3.95 6.61 5.13 9.52 3.95 1.48-.59 2.15 1.4.65 2.05-2.34.99-8.77.89-11.78-4.32-2.03-3.52-1.93-9.71 3.46-12.92C10.81 1.42 16.24 2.1 19.5 5.5c3.45 3.6 3.25 10.3-.1 12.91-1.51 1.18-3.76.03-3.74-1.7l-.02-.56a5.611 5.611 0 0 1-3.99 1.66C8.63 17.81 6 15.15 6 12.13c0-3.05 2.63-5.74 5.65-5.74z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="react" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.11c1.03 0 1.87.84 1.87 1.89 0 1-.84 1.85-1.87 1.85-1.03 0-1.87-.85-1.87-1.85 0-1.05.84-1.89 1.87-1.89M7.37 20c.63.38 2.01-.2 3.6-1.7-.52-.59-1.03-1.23-1.51-1.9a22.7 22.7 0 0 1-2.4-.36c-.51 2.14-.32 3.61.31 3.96m.71-5.74l-.29-.51c-.11.29-.22.58-.29.86.27.06.57.11.88.16l-.3-.51m6.54-.76l.81-1.5-.81-1.5c-.3-.53-.62-1-.91-1.47C13.17 9 12.6 9 12 9c-.6 0-1.17 0-1.71.03-.29.47-.61.94-.91 1.47L8.57 12l.81 1.5c.3.53.62 1 .91 1.47.54.03 1.11.03 1.71.03.6 0 1.17 0 1.71-.03.29-.47.61-.94.91-1.47M12 6.78c-.19.22-.39.45-.59.72h1.18c-.2-.27-.4-.5-.59-.72m0 10.44c.19-.22.39-.45.59-.72h-1.18c.2.27.4.5.59.72M16.62 4c-.62-.38-2 .2-3.59 1.7.52.59 1.03 1.23 1.51 1.9.82.08 1.63.2 2.4.36.51-2.14.32-3.61-.32-3.96m-.7 5.74l.29.51c.11-.29.22-.58.29-.86-.27-.06-.57-.11-.88-.16l.3.51m1.45-7.05c1.47.84 1.63 3.05 1.01 5.63 2.54.75 4.37 1.99 4.37 3.68 0 1.69-1.83 2.93-4.37 3.68.62 2.58.46 4.79-1.01 5.63-1.46.84-3.45-.12-5.37-1.95-1.92 1.83-3.91 2.79-5.38 1.95-1.46-.84-1.62-3.05-1-5.63-2.54-.75-4.37-1.99-4.37-3.68 0-1.69 1.83-2.93 4.37-3.68-.62-2.58-.46-4.79 1-5.63 1.47-.84 3.46.12 5.38 1.95 1.92-1.83 3.91-2.79 5.37-1.95M17.08 12c.34.75.64 1.5.89 2.26 2.1-.63 3.28-1.53 3.28-2.26 0-.73-1.18-1.63-3.28-2.26-.25.76-.55 1.51-.89 2.26M6.92 12c-.34-.75-.64-1.5-.89-2.26-2.1.63-3.28 1.53-3.28 2.26 0 .73 1.18 1.63 3.28 2.26.25-.76.55-1.51.89-2.26m9 2.26l-.3.51c.31-.05.61-.1.88-.16-.07-.28-.18-.57-.29-.86l-.29.51m-2.89 4.04c1.59 1.5 2.97 2.08 3.59 1.7.64-.35.83-1.82.32-3.96-.77.16-1.58.28-2.4.36-.48.67-.99 1.31-1.51 1.9M8.08 9.74l.3-.51c-.31.05-.61.1-.88.16.07.28.18.57.29.86l.29-.51m2.89-4.04C9.38 4.2 8 3.62 7.37 4c-.63.35-.82 1.82-.31 3.96a22.7 22.7 0 0 1 2.4-.36c.48-.67.99-1.31 1.51-1.9z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="readme" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="reason" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm5.119 8.993h2.798c.382 0 .71.025.985.075.275.05.534.159.774.326.244.168.435.386.577.654.145.265.218.598.218 1 0 .552-.112 1.001-.335 1.35-.22.348-.536.638-.947.87l2.16 3.203H12.31l-1.763-2.742h-.77v2.742H8.12v-7.478zm6.594 0h4.676v1.447h-3.018v1.29h2.802v1.447h-2.802v1.848h3.018v1.446h-4.676v-7.478zM9.778 13.37v2.014h.513c.266 0 .49-.014.67-.044.18-.03.329-.1.45-.207a.96.96 0 0 0 .253-.34c.055-.128.082-.297.082-.508 0-.187-.034-.35-.1-.483a.698.698 0 0 0-.343-.317 1.086 1.086 0 0 0-.395-.095 6.012 6.012 0 0 0-.526-.02h-.604z" fill="#f44336" stroke-width="1.067"/></symbol><symbol viewBox="0 0 172 193" id="restql" xmlns="http://www.w3.org/2000/svg"><title>Group</title><g transform="translate(14.767 16.713) scale(.82795)" fill="none"><path d="M171.39 55.799c-.975-6.147-4.673-11.642-10.15-14.805L96.381 3.546C93.217 1.72 89.615.756 85.964.756s-7.253.964-10.415 2.788L10.69 40.992A20.896 20.896 0 0 0 .272 59.035v74.89a20.894 20.894 0 0 0 10.416 18.042l64.859 37.446c3.165 1.827 6.767 2.791 10.417 2.791s7.252-.964 10.415-2.79l64.859-37.445c5.479-3.166 9.178-8.66 10.152-14.808zm-16.516 85.147L90.017 178.39a8.104 8.104 0 0 1-8.108 0l-64.857-37.444a8.109 8.109 0 0 1-4.053-7.021v-74.89a8.109 8.109 0 0 1 4.053-7.021l64.857-37.446c1.254-.725 2.654-1.086 4.054-1.086s2.8.361 4.054 1.086l64.857 37.446a8.106 8.106 0 0 1 4.053 7.021v74.89a8.109 8.109 0 0 1-4.053 7.021z" fill="#83e8c2"/><path d="M158.93 59.035a8.109 8.109 0 0 0-4.053-7.021L90.02 14.568c-1.254-.725-2.654-1.086-4.054-1.086s-2.8.361-4.054 1.086L17.055 52.014a8.106 8.106 0 0 0-4.053 7.021v74.89a8.109 8.109 0 0 0 4.053 7.021l64.857 37.444a8.104 8.104 0 0 0 8.108 0l64.857-37.444a8.109 8.109 0 0 0 4.053-7.021zm-46.766 31.681c.119-.069.242-.118.365-.149.044-.012.088-.01.131-.018.076-.012.152-.029.228-.029l.015.001c.02.001.038.005.059.006.093.005.184.019.273.04l.1.03c.077.025.15.057.223.095.028.014.057.027.084.043.094.057.184.122.263.199.007.008.013.017.021.024.07.071.133.15.188.235.018.029.033.059.05.09.04.072.072.148.099.229a1.512 1.512 0 0 1 .081.46v16.209l-3.278 1.893a1.548 1.548 0 0 0-.678.83 1.533 1.533 0 0 0-.098.514v3.785l-14.038 8.104-.01.004a1.55 1.55 0 0 1-.354.146c-.045.012-.09.011-.135.018-.074.012-.15.029-.225.029l-.014-.001c-.02-.001-.039-.005-.059-.006a1.463 1.463 0 0 1-.273-.041c-.034-.008-.066-.019-.1-.03a1.318 1.318 0 0 1-.223-.094c-.029-.015-.057-.027-.084-.044a1.45 1.45 0 0 1-.263-.198c-.009-.008-.015-.019-.023-.027a1.495 1.495 0 0 1-.185-.232c-.019-.029-.034-.06-.051-.09a1.422 1.422 0 0 1-.098-.229 1.702 1.702 0 0 1-.033-.101 1.487 1.487 0 0 1-.048-.358l-.001-.002v-20.053a1.446 1.446 0 0 1 .727-1.255zM85.24 31.369a1.449 1.449 0 0 1 1.452 0l45.741 26.41a1.45 1.45 0 0 1 0 2.512l-17.366 10.027a1.457 1.457 0 0 1-1.452 0l-15.49-8.943 1.727-.996a1.552 1.552 0 0 0 0-2.688l-13.111-7.57c-.239-.139-.508-.207-.775-.207s-.535.068-.775.207l-3.278 1.893-14.038-8.104a1.451 1.451 0 0 1 0-2.513zM57.59 47.558c.251 0 .501.065.726.194l15.489 8.942-1.727.997a1.552 1.552 0 0 0 0 2.688l1.727.996-15.488 8.943a1.457 1.457 0 0 1-1.452 0L39.499 60.291a1.45 1.45 0 0 1 0-2.512l17.366-10.027c.225-.129.475-.194.725-.194zm-9.56 92.328c-.241 0-.489-.062-.724-.196l-17.365-10.026a1.45 1.45 0 0 1-.726-1.256V75.59c0-.847.694-1.453 1.452-1.453.242 0 .49.062.724.197l17.366 10.025c.449.26.726.738.726 1.257v17.886l-1.727-.997a1.552 1.552 0 0 0-2.327 1.344v15.139c0 .555.295 1.067.775 1.344l3.278 1.894v16.209a1.45 1.45 0 0 1-1.452 1.451zm29.828 14.929a1.452 1.452 0 0 1-2.177 1.257l-17.365-10.026a1.452 1.452 0 0 1-.726-1.257v-17.885l1.726.996c.25.145.515.211.773.211.811 0 1.554-.648 1.554-1.555v-1.993l15.489 8.942c.449.26.726.738.726 1.257zm0-32.768c0 .127-.02.246-.049.36-.009.035-.021.067-.032.101-.026.08-.059.157-.099.229-.017.03-.032.061-.05.09a1.48 1.48 0 0 1-.188.235l-.021.025a1.51 1.51 0 0 1-.264.199c-.026.016-.055.028-.082.043a1.597 1.597 0 0 1-.324.124 1.362 1.362 0 0 1-.278.041c-.018.001-.036.006-.055.006l-.015.001c-.077 0-.155-.018-.233-.03-.043-.007-.084-.005-.125-.017a1.484 1.484 0 0 1-.366-.149l-14.035-8.104v-3.784a1.545 1.545 0 0 0-.776-1.343l-3.276-1.892V91.976c0-.127.02-.246.049-.361.009-.034.021-.066.032-.1a1.33 1.33 0 0 1 .099-.229c.017-.03.032-.062.051-.091.054-.084.116-.163.187-.234l.021-.025c.079-.076.168-.142.263-.199.027-.016.056-.029.084-.043a1.476 1.476 0 0 1 .601-.166c.019 0 .036-.005.055-.005l.015-.001c.078 0 .157.018.236.03.04.007.081.005.122.017.124.031.246.08.366.149l17.361 10.023a1.456 1.456 0 0 1 .726 1.259zm-9.984-45.373a1.448 1.448 0 0 1-.544-.55 1.466 1.466 0 0 1 0-1.413c.121-.219.303-.41.544-.55l14.038-8.104 3.277 1.892c.48.276 1.071.276 1.551 0l3.278-1.893 14.038 8.105a1.45 1.45 0 0 1 0 2.513L86.691 86.7a1.447 1.447 0 0 1-1.452 0zm74.842 51.733c0 .518-.276.997-.726 1.256l-45.741 26.409a1.452 1.452 0 0 1-2.177-1.257v-20.053c0-.519.277-.997.727-1.257l15.488-8.941v1.992c0 .906.743 1.555 1.553 1.555.26 0 .523-.066.774-.21l13.11-7.57a1.55 1.55 0 0 0 .776-1.344v-3.784l14.038-8.105a1.452 1.452 0 0 1 2.177 1.257v20.052zm0-32.764c0 .519-.276.997-.726 1.256l-15.489 8.943v-1.993c0-.906-.744-1.554-1.554-1.554a1.519 1.519 0 0 0-.773.21l-1.727.996V85.616c0-.519.277-.997.727-1.257l17.365-10.025c.234-.135.482-.197.724-.197.758 0 1.453.606 1.453 1.453z" fill="#111d5a"/><g fill="#83e8c2"><path d="M59.402 90.568zM94.485 123.06zM94.771 123.29zM77.775 122.51zM77.072 123.33zM77.418 123.09zM77.856 122.05zM76.749 123.45zM94.119 122.41zM77.131 133.51l-15.489-8.942v1.993c0 .906-.743 1.555-1.554 1.555a1.53 1.53 0 0 1-.773-.211l-1.726-.996v17.885c0 .519.276.997.726 1.257l17.365 10.026a1.452 1.452 0 0 0 2.177-1.257v-20.053a1.454 1.454 0 0 0-.726-1.257zM94.25 122.74zM110.28 111.42zM94.494 100.98c.088-.089.189-.168.303-.232l17.365-10.026-17.365 10.026a1.392 1.392 0 0 0-.303.232zM77.627 122.83zM58.027 90.936zM58.374 90.693zM59.044 90.521l-.015.001c.083-.001.167.015.251.029-.079-.012-.158-.03-.236-.03zM57.819 91.195zM58.696 90.568zM57.589 91.977zM76.043 123.46zM57.67 91.516zM75.677 123.31l-14.035-8.11zM76.401 123.5l.015-.001c-.082.001-.166-.016-.248-.029.078.012.156.03.233.03zM112.16 90.716zM77.662 101.27zM113.64 90.734zM96.237 123.31zM113.33 90.597zM112.89 90.52c-.075 0-.151.018-.228.029.081-.014.162-.029.242-.028l-.014-.001zM141.26 74.137c-.241 0-.489.062-.724.197l-17.365 10.025c-.449.26-.727.738-.727 1.257v17.885l1.727-.996c.25-.145.515-.211.773-.21.81 0 1.554.647 1.554 1.554v1.993l15.489-8.943a1.45 1.45 0 0 0 .726-1.256V75.59c0-.847-.695-1.453-1.453-1.453zM112.96 90.526zM95.523 123.5c.074 0 .15-.018.225-.029-.08.013-.159.028-.238.028l.013.001zM95.451 123.5zM85.238 86.7zM95.078 123.43zM141.26 106.9c-.241 0-.489.062-.724.196l-14.038 8.105v3.784c0 .555-.296 1.067-.776 1.344l-13.11 7.57c-.251.144-.515.21-.774.21-.81 0-1.553-.648-1.553-1.555v-1.992l-15.488 8.941c-.449.26-.727.738-.727 1.257v20.053a1.452 1.452 0 0 0 2.177 1.257l45.741-26.409a1.45 1.45 0 0 0 .726-1.256v-20.053a1.454 1.454 0 0 0-1.454-1.452zM67.871 41.396a1.451 1.451 0 0 0 0 2.513l14.038 8.104 3.278-1.893c.24-.139.508-.207.775-.207s.536.068.775.207l13.111 7.57a1.552 1.552 0 0 1 0 2.688l-1.727.996 15.49 8.943a1.457 1.457 0 0 0 1.452 0l17.366-10.027a1.45 1.45 0 0 0 0-2.512l-45.741-26.41a1.449 1.449 0 0 0-1.452 0zM39.497 57.779a1.45 1.45 0 0 0 0 2.512l17.366 10.027a1.457 1.457 0 0 0 1.452 0l15.488-8.943-1.727-.996a1.552 1.552 0 0 1 0-2.688l1.727-.997-15.489-8.942a1.458 1.458 0 0 0-1.451 0zM49.481 138.43v-16.209l-3.278-1.894a1.55 1.55 0 0 1-.775-1.344v-15.139c0-.906.743-1.555 1.554-1.554.259 0 .523.065.773.21l1.727.997V85.611a1.45 1.45 0 0 0-.726-1.257L31.39 74.33a1.436 1.436 0 0 0-.724-.197c-.758 0-1.452.606-1.452 1.453v52.817c0 .518.276.997.726 1.256l17.365 10.026a1.45 1.45 0 0 0 2.176-1.255zM114.34 108.18l-3.278 1.893 3.278-1.893V91.971zM114.11 91.193zM114.16 91.283z"/></g><g fill="#de5941"><path d="M94.494 100.98a1.45 1.45 0 0 0-.424 1.023v20.053l.001.002c0 .126.02.244.048.358.01.034.021.066.033.101.026.08.059.156.098.229.017.03.032.061.051.09.055.084.115.162.185.232.009.009.015.02.023.027.079.077.169.142.263.198.027.017.055.029.084.044a1.46 1.46 0 0 0 .596.165c.02.001.039.005.059.006.079 0 .158-.016.238-.028.045-.007.09-.006.135-.018.119-.031.238-.08.354-.146l.01-.004 14.038-8.104v-3.785c0-.18.04-.35.098-.514.122-.343.353-.643.678-.83l3.278-1.893V91.977c0-.127-.021-.246-.049-.361-.009-.033-.021-.065-.032-.099a1.266 1.266 0 0 0-.099-.229c-.017-.031-.032-.061-.05-.09a1.425 1.425 0 0 0-.188-.235l-.021-.024a1.41 1.41 0 0 0-.263-.199c-.027-.016-.056-.029-.084-.043a1.509 1.509 0 0 0-.323-.125 1.591 1.591 0 0 0-.273-.04c-.021-.001-.039-.005-.059-.006-.08-.001-.161.015-.242.028-.043.008-.087.006-.131.018-.123.031-.246.08-.365.149l-17.365 10.026a1.447 1.447 0 0 0-.302.233zM77.13 100.74L59.769 90.717a1.424 1.424 0 0 0-.366-.149c-.041-.012-.082-.01-.122-.017-.084-.015-.168-.03-.251-.029-.019 0-.036.005-.055.005-.095.005-.188.02-.278.041-.034.009-.065.02-.099.03a1.406 1.406 0 0 0-.224.095c-.028.014-.057.027-.084.043a1.515 1.515 0 0 0-.263.199l-.021.025c-.07.071-.133.15-.187.234-.019.029-.034.061-.051.091-.04.073-.072.149-.099.229a1.463 1.463 0 0 0-.081.461v16.206l3.276 1.892a1.547 1.547 0 0 1 .776 1.343v3.784l14.035 8.104c.119.068.242.117.366.149.041.012.082.01.125.017.082.014.166.03.248.029.019 0 .037-.005.055-.006.095-.004.188-.019.278-.041.034-.008.065-.019.099-.029.077-.025.152-.058.225-.095.027-.015.056-.027.082-.043.095-.058.185-.123.264-.199l.021-.025c.07-.071.133-.15.188-.235.018-.029.033-.06.05-.09.04-.072.072-.149.099-.229a1.448 1.448 0 0 0 .081-.461v-20.047a1.456 1.456 0 0 0-.726-1.259zM86.689 86.7l17.365-10.026a1.45 1.45 0 0 0 0-2.513l-14.038-8.105-3.278 1.893a1.556 1.556 0 0 1-1.551 0l-3.277-1.892-14.038 8.104c-.241.14-.423.331-.544.55a1.466 1.466 0 0 0 0 1.413c.121.218.303.41.544.55L85.238 86.7a1.447 1.447 0 0 0 1.451 0z"/></g></g></symbol><symbol viewBox="0 0 24 24" id="riot" xmlns="http://www.w3.org/2000/svg"><defs><path d="M13.26 3.04l.58.05.54.07.52.09.49.11.46.13.44.14.41.16.39.17.36.19.33.21.32.22.29.23.26.25.22.22.2.22.19.24.17.24.15.25.15.26.12.27.12.28.1.29.08.31.07.31.05.32.04.34.02.35.01.37v.05l-.02.51-.05.49-.09.48-.13.45-.15.43-.19.4-.22.39-.26.37-.28.34-.31.33-.33.3-.37.28-.39.27-.41.24-.44.22L21 21h-7.04l-3.48-5.14H9.17V21H3V3h9.01l.64.01.61.03zm-4.09 8.52h2.66l.99-.11.75-.35.47-.55.16-.74v-.05l-.17-.75-.47-.54-.74-.32-.96-.11H9.17v3.52z" id="ija"/></defs><use xlink:href="#ija" fill="#ff1744"/><use xlink:href="#ija" fill-opacity="0" stroke="#000" stroke-opacity="0"/></symbol><symbol viewBox="0 0 24 24" id="robot" xmlns="http://www.w3.org/2000/svg"><path d="M12.05 2.804a1.787 1.787 0 0 1 1.788 1.788c0 .661-.357 1.242-.893 1.546v1.135h.893a6.256 6.256 0 0 1 6.256 6.256h.894a.894.894 0 0 1 .893.893v2.681a.894.894 0 0 1-.893.894h-.894v.894a1.787 1.787 0 0 1-1.787 1.787H5.795a1.787 1.787 0 0 1-1.787-1.787v-.894h-.894a.894.894 0 0 1-.894-.894v-2.68a.894.894 0 0 1 .894-.894h.894a6.256 6.256 0 0 1 6.255-6.256h.894V6.138a1.773 1.773 0 0 1-.894-1.546 1.787 1.787 0 0 1 1.788-1.788m-4.022 9.83a2.234 2.234 0 0 0-2.234 2.235 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.234-2.234 2.234 2.234 0 0 0-2.234-2.234m8.043 0a2.234 2.234 0 0 0-2.234 2.234 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.235-2.234 2.234 2.234 0 0 0-2.235-2.234z" fill="#ff5722" stroke-width=".894"/></symbol><symbol viewBox="100 100 800 800" id="rollup" xmlns="http://www.w3.org/2000/svg"><style>.ilst0{fill:url(#ilXMLID_4_)}.ilst1{fill:url(#ilXMLID_5_)}.ilst2{fill:url(#ilXMLID_8_)}.ilst3{fill:url(#ilXMLID_9_)}.ilst4{fill:url(#ilXMLID_11_)}.ilst5{opacity:.3;fill:url(#ilXMLID_16_)}</style><g id="ilXMLID_14_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_4_" x1="444.47" x2="598.47" y1="526.05" y2="562.05" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_15_" class="ilst0" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_4_)"/></g><g id="ilXMLID_2_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_5_" x1="420.38" x2="696.38" y1="475" y2="689" gradientUnits="userSpaceOnUse"><stop stop-color="#BF3338" offset="0"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_10_" class="ilst1" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_5_)"/></g><linearGradient id="ilXMLID_8_" x1="429.39" x2="469.39" y1="517.16" y2="559.16" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_3_" class="ilst2" d="M329.82 813.46c15.58-8.903 122.41-220.34 227.02-320.5s117.96-66.771 60.094-175.83c0 0-221.46 310.49-301.58 464.06" fill="url(#ilXMLID_8_)" stroke-width="1.113"/><g id="ilXMLID_7_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_9_" x1="502.11" x2="490.11" y1="589.46" y2="417.46" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_12_" class="ilst3" d="M373 537c134.4-247.1 152-272 222-272 36.8 0 73.9 16.6 97.9 46.1-32.7-52.7-90.6-88-156.9-89H307.7c-4.8 0-8.7 3.9-8.7 8.7V691c13.6-35.1 36.7-85.3 74-154z" fill="url(#ilXMLID_9_)"/></g><linearGradient id="ilXMLID_11_" x1="450.12" x2="506.94" y1="514.21" y2="552.85" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FBB040" offset="0"/><stop stop-color="#FB8840" offset="1"/></linearGradient><path id="ilXMLID_6_" class="ilst4" d="M556.84 492.96c-104.61 100.16-211.44 311.6-227.02 320.5s-41.732 10.016-55.643-5.564c-14.801-16.582-37.837-43.401 86.802-272.65 149.57-274.99 169.15-302.7 247.05-302.7 40.953 0 82.24 18.473 108.95 51.302 1.447 2.337 2.893 4.785 4.34 7.233-45.738-47.074-145.23-57.98-169.93-.222-25.373 59.204 42.622 125.08 72.335 119.85 37.837-6.677-6.677-93.48-6.677-93.48 57.757 108.95 44.403 75.563-60.205 175.72z" fill="url(#ilXMLID_11_)" stroke-width="1.113"/><linearGradient id="ilXMLID_16_" x1="508.33" x2="450.33" y1="295.76" y2="933.76" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFF" offset="0"/><stop stop-color="#FFF" stop-opacity="0" offset="1"/></linearGradient><path id="ilXMLID_13_" class="ilst5" d="M373.22 547.49c149.57-274.99 169.15-302.7 247.05-302.7 33.719 0 67.661 12.575 93.48 35.277-26.708-30.492-66.326-47.519-105.72-47.519-77.9 0-97.486 27.71-247.05 302.7-124.64 229.25-101.6 256.07-86.802 272.65 2.114 2.337 4.563 4.34 7.122 6.01-13.02-18.919-18.807-62.877 91.922-266.42z" fill="url(#ilXMLID_16_)" opacity=".3" stroke-width="1.113"/></symbol><symbol viewBox="0 0 24 24" id="ruby" xmlns="http://www.w3.org/2000/svg"><path d="M16 9h3l-5 7m-4-7h4l-2 8M5 9h3l2 7m5-12h2l2 3h-3m-5-3h2l1 3h-4M7 4h2L8 7H5m1-5L2 8l10 14L22 8l-4-6H6z" fill="#f44336"/></symbol><symbol viewBox="0 0 144 144" id="rust" xmlns="http://www.w3.org/2000/svg"><path d="M68.252 26.206a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0M25.766 58.451a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m84.97.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m-74.661 4.88a3.252 3.252 0 0 0 1.651-4.29l-1.58-3.574h6.214v28.01H29.823a43.847 43.847 0 0 1-1.42-16.738zm25.994.688v-8.256h14.798c.764 0 5.397.883 5.397 4.347 0 2.877-3.553 3.908-6.475 3.908zm-20.203 44.452a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m52.769.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m1.101-8.076a3.246 3.246 0 0 0-3.856 2.498l-1.787 8.342a43.847 43.847 0 0 1-36.566-.175l-1.787-8.342a3.246 3.246 0 0 0-3.854-2.497l-7.365 1.581a43.847 43.847 0 0 1-3.808-4.488h35.834c.406 0 .676-.074.676-.443V84.527c0-.369-.27-.442-.676-.442h-10.48V76.05h11.335c1.035 0 5.532.296 6.97 6.045.45 1.768 1.44 7.519 2.116 9.36.674 2.065 3.417 6.19 6.34 6.19h18.501a43.847 43.847 0 0 1-4.06 4.7zm19.898-33.468a43.847 43.847 0 0 1 .093 7.612h-4.499c-.45 0-.631.296-.631.737v2.066c0 4.863-2.742 5.92-5.145 6.19-2.288.258-4.825-.958-5.138-2.358-1.35-7.593-3.6-9.214-7.152-12.016 4.409-2.8 8.996-6.93 8.996-12.457 0-5.97-4.092-9.729-6.881-11.572-3.914-2.58-8.246-3.096-9.415-3.096H39.336A43.847 43.847 0 0 1 63.867 28.52l5.484 5.753a3.243 3.243 0 0 0 4.59.105l6.137-5.869a43.847 43.847 0 0 1 30.017 21.38l-4.201 9.487a3.256 3.256 0 0 0 1.652 4.29zm10.477.154l-.143-1.467 4.327-4.036c.88-.82.55-2.472-.574-2.891l-5.532-2.068-.433-1.428 3.45-4.792c.704-.974.058-2.53-1.127-2.724l-5.833-.949-.7-1.31 2.45-5.38c.502-1.095-.43-2.496-1.636-2.45l-5.92.206-.935-1.135 1.36-5.766c.275-1.17-.913-2.36-2.084-2.085l-5.765 1.359-1.136-.935.207-5.92c.046-1.198-1.357-2.135-2.45-1.637l-5.379 2.452-1.31-.703-.95-5.833c-.193-1.183-1.75-1.83-2.723-1.128l-4.796 3.45-1.425-.432-2.068-5.532c-.42-1.127-2.072-1.452-2.89-.576l-4.036 4.33-1.467-.143-3.117-5.036c-.63-1.02-2.318-1.02-2.946 0l-3.117 5.036-1.467.143-4.037-4.33c-.819-.876-2.47-.551-2.89.576l-2.069 5.532-1.426.432-4.795-3.45c-.974-.703-2.53-.055-2.723 1.128l-.951 5.833-1.31.703-5.379-2.452c-1.093-.5-2.496.439-2.45 1.637l.206 5.92-1.136.935-5.765-1.36c-1.171-.272-2.36.915-2.086 2.086l1.358 5.766-.933 1.135-5.92-.206c-1.193-.035-2.134 1.355-1.637 2.45l2.453 5.38-.703 1.31-5.832.949c-1.185.192-1.827 1.75-1.128 2.724l3.45 4.792-.433 1.428-5.532 2.068c-1.123.42-1.452 2.07-.574 2.891l4.328 4.036-.143 1.467-5.035 3.116c-1.02.63-1.02 2.318 0 2.946l5.035 3.117.143 1.467-4.328 4.037c-.878.818-.549 2.468.574 2.89l5.532 2.068.433 1.428-3.45 4.793c-.701.976-.056 2.532 1.129 2.723l5.831.948.703 1.312-2.453 5.378c-.5 1.093.444 2.5 1.638 2.451l5.917-.207.935 1.136-1.358 5.768c-.275 1.168.915 2.355 2.086 2.08l5.765-1.357 1.137.932-.207 5.921c-.046 1.199 1.357 2.136 2.45 1.636l5.379-2.45 1.31.702.95 5.83c.193 1.187 1.75 1.829 2.725 1.13l4.792-3.453 1.427.435 2.069 5.53c.42 1.123 2.072 1.454 2.89.574l4.037-4.328 1.467.146 3.117 5.035c.628 1.016 2.316 1.018 2.946 0l3.117-5.035 1.467-.146 4.036 4.328c.818.88 2.47.549 2.89-.574l2.068-5.53 1.428-.435 4.793 3.453c.974.699 2.53.055 2.722-1.13l.952-5.83 1.31-.703 5.378 2.451c1.093.5 2.493-.435 2.45-1.636l-.206-5.92 1.135-.933 5.765 1.357c1.171.275 2.36-.912 2.085-2.08l-1.358-5.768.932-1.136 5.92.207c1.194.048 2.138-1.358 1.636-2.451l-2.45-5.378.7-1.312 5.833-.948c1.187-.19 1.831-1.747 1.127-2.723l-3.45-4.793.433-1.428 5.532-2.068c1.125-.422 1.454-2.072.574-2.89l-4.327-4.037.143-1.467 5.035-3.117c1.02-.628 1.021-2.315.001-2.946z" fill="#ff7043" stroke-width="1.146"/></symbol><symbol viewBox="0 0 500 500" id="sass" xmlns="http://www.w3.org/2000/svg"><path d="M422.676 96.573c-12.192-47.839-91.508-63.557-166.575-36.892-44.68 15.877-93.029 40.786-127.81 73.311-41.349 38.675-47.943 72.328-45.216 86.395 9.583 49.622 77.585 82.069 105.535 106.126v.144c-8.246 4.05-68.565 34.584-82.684 65.799-14.893 32.932 2.372 56.556 13.804 59.742 35.424 9.859 71.764-7.866 91.311-37.01 18.853-28.12 17.28-64.422 9.086-82.487 11.3-2.976 24.476-4.314 41.218-2.36 47.248 5.52 56.517 35.017 54.747 47.366-1.77 12.35-11.681 19.14-14.998 21.186-3.317 2.045-4.326 2.766-4.05 4.287.405 2.215 1.94 2.137 4.758 1.652 3.894-.656 24.804-10.042 25.709-32.828 1.14-28.933-26.587-61.302-75.684-60.45-20.216.354-32.933 2.268-42.123 5.69-.681-.774-1.363-1.547-2.084-2.307-30.35-32.382-86.46-55.285-84.088-98.824.866-15.823 6.372-57.5 107.817-108.052 83.104-41.415 149.637-30.009 161.135-4.76 16.427 36.08-35.554 103.137-121.858 112.812-32.88 3.684-50.198-9.059-54.498-13.804-4.536-4.995-5.204-5.218-6.909-4.287-2.753 1.533-1.01 5.938 0 8.574 2.583 6.712 13.15 18.603 31.176 24.515 15.863 5.205 54.459 8.063 101.156-9.99 52.283-20.255 93.12-76.523 81.125-123.548zM200.213 340.34c3.92 14.5 3.487 28.016-.564 40.248a65.289 65.289 0 0 1-3.225 7.97c-3.12 6.477-7.316 12.534-12.442 18.132-15.653 17.069-37.507 23.532-46.88 18.092-10.122-5.874-5.048-29.944 13.083-49.11 19.52-20.636 47.602-33.903 47.602-33.903l-.039-.079 2.465-1.35z" fill="#ec407a" stroke="#ec407a" stroke-width="16.286552999999998"/></symbol><symbol viewBox="0 0 300 300" id="sbt" xmlns="http://www.w3.org/2000/svg"><path d="M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z" fill="#0277bd" stroke-width="1.638"/></symbol><symbol viewBox="0 0 256 256" id="scala" xmlns="http://www.w3.org/2000/svg"><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M59.607 50.647l149.097-21.982v49.488L59.607 100.135zM59.593 114.08L208.69 92.098v49.488L59.593 163.568zM59.587 177.358l149.097-21.982v49.488L59.587 226.846z"/><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M62.425 91.414l95.605 30.923-2.832 8.757-95.605-30.922zM113.084 61.13l95.604 30.922-2.832 8.757-95.605-30.922zM62.425 154.79l95.605 30.922-2.833 8.758-95.604-30.923zM113.097 124.408l95.604 30.923-2.832 8.757-95.605-30.922z"/></symbol><symbol viewBox="0 0 24 24" id="settings" xmlns="http://www.w3.org/2000/svg"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="shaderlab" xmlns="http://www.w3.org/2000/svg"><path d="M9.11 17H6.5l-4.91-5L6.5 7h2.61l1.31-2.26L17.21 3l1.87 6.74L17.77 12l1.31 2.26L17.21 21l-6.79-1.74L9.11 17m.14-.25l5.13 1.38L11.42 13H5.5l3.75 3.75m6.87.38L17.5 12l-1.38-5.13L13.15 12l2.97 5.13M9.25 7.25L5.5 11h5.92l2.96-5.13-5.13 1.38z" fill="#1976d2"/></symbol><symbol viewBox="0 0 24 24" id="slim" xmlns="http://www.w3.org/2000/svg"><path d="M6.959 2.5a4.605 4.605 0 0 0-4.615 4.615v9.957a4.605 4.605 0 0 0 4.615 4.615h9.957a4.605 4.605 0 0 0 4.615-4.615V7.115A4.605 4.605 0 0 0 16.916 2.5zm4.938 2.691a6.811 6.811 0 0 1 6.81 6.813H13.43L9.938 7.287l.699 4.717H5.086a6.811 6.811 0 0 1 6.81-6.813z" fill="#f57f17"/></symbol><symbol id="smarty" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.iust0{fill:#ffce00}</style><path class="iust0" d="M9.14 20.606c0 .556.398.953.954.953h3.812c.556 0 .953-.397.953-.953v-.953H9.141zM12 2.5c-3.653 0-6.671 3.018-6.671 6.671 0 2.303 1.112 4.289 2.859 5.48v2.144c0 .556.397.953.953.953h5.718c.556 0 .953-.397.953-.953V14.65c1.747-1.191 2.86-3.177 2.86-5.48 0-3.653-3.019-6.671-6.672-6.671zm2.7 10.563l-.794.555v2.224h-3.812v-2.224l-.794-.555A4.712 4.712 0 0 1 7.235 9.17 4.78 4.78 0 0 1 12 4.405a4.78 4.78 0 0 1 4.765 4.765 4.712 4.712 0 0 1-2.065 3.892z"/></symbol><symbol viewBox="0 0 200 200" id="snyk" xmlns="http://www.w3.org/2000/svg"><title>Group 2</title><g transform="translate(15.255 18.22) scale(1.8477)" fill="none" fill-rule="evenodd"><path d="M65.161 24.997c-1.656 5.974-5.255 23.587-5.255 23.587s-6.618-2.464-14.148-2.476h-.055c-.413.002-.822.012-1.23.026v41.649h6.677v.003h5.815v-.003h20.858c.111-8.177-2.036-27.066-2.036-27.066-1.088-2.279.46-7.668.46-7.668-8.869-9.092-11.086-28.051-11.086-28.051zm-3.357 43.958c5.476 0 1.381 4.64.9 5.168H52.35c.944-1.18 4.504-5.168 9.453-5.168z" fill="#607d8b" stroke-width="1.6"/><path d="M26.366 24.995s-2.217 18.961-11.087 28.053c0 0 1.548 5.391.46 7.669 0 0-2.15 18.895-2.038 27.066h19.273v.003h7.079v-.003h5.744V46.107h-.025c-7.532.013-14.151 2.478-14.151 2.478s-3.6-17.615-5.255-23.59zm3.264 43.96c4.95 0 8.51 3.987 9.452 5.168H28.73c-.479-.528-4.573-5.168.9-5.168z" fill="#90a4ae" stroke-width="1.6"/><g transform="translate(23.76 77.45) scale(1.5998)"><g transform="translate(17.526)"><path d="M7.357.06H.177v.075C.177 2.64 2.345 4.67 4.89 4.67 7.431 4.67 9.6 2.64 9.6.135V.059z" fill="#455a64"/><path d="M1.972.06v.075a2.692 2.692 0 1 0 5.386 0V.059z" fill="#fff"/><path d="M5.496.06H4.234c-.012 0-.023.005-.034.007.157.033.243.388.21.624a.721.721 0 0 1-.71.617c.102.471.487.85.997.922a1.188 1.188 0 0 0 1.35-1.007C6.112.743 5.881.06 5.495.06z" fill="#37474f"/></g><path d="M7.552.06H.372v.075c0 2.505 2.17 4.535 4.712 4.535 2.544 0 4.712-2.03 4.712-4.535V.059z" fill="#455a64"/><path d="M2.168.06v.075a2.692 2.692 0 1 0 5.385 0V.059z" fill="#fff"/><path d="M5.692.06H4.428c-.01 0-.022.005-.032.007.156.033.242.388.21.624a.72.72 0 0 1-.712.617c.104.471.488.85.999.922A1.187 1.187 0 0 0 6.24 1.223C6.308.743 6.078.06 5.69.06z" fill="#37474f"/></g><path d="M25.514-.27l-4.202 7.697C19.838 10.17 6.858 34.465 6.858 43.243v.516L12.8 59.573c-.8 7.258-2.203 21.643-1.78 28.21h5.73c-.354-3.787.648-17.008 1.903-28.25l.076-.677-1.075-2.892c3.694-3.868 6.285-9.193 8.073-14.261l.174 1.235 5.869 9.629 2.291-.983c.058-.024 5.935-2.523 11.643-2.523 5.672 0 11.646 2.5 11.702 2.525l2.29.976 5.86-9.626.23-1.608c1.769 5.117 4.358 10.536 8.07 14.49l-1.127 3.035.076.678c1.259 11.286 2.266 24.564 1.916 28.252h5.677c.406-6.567-1.05-20.952-1.848-28.208l5.838-15.817v-.514c0-8.779-12.876-33.074-14.347-35.816L65.923-.27l-5.897 41.229-2.723 4.478c-2.628-.882-7.1-2.11-11.603-2.11-4.498 0-8.94 1.225-11.557 2.108l-2.722-4.476-2.07-14.452a.832.832 0 0 0 .006-.071l-.016-.004zm-3.166 18.39l1.206 8.407c-.46 3.143-2.561 15.47-8.198 23.24l-2.598-6.99c.325-4.554 5.067-15.462 9.59-24.656zm46.763 0c4.523 9.194 9.267 20.104 9.592 24.657L76.166 49.6c-6.09-8.553-8-22.459-8.166-23.73z" fill="#607d8b" stroke-width="1.6"/></g></symbol><symbol viewBox="0 0 24 24" id="solidity" xmlns="http://www.w3.org/2000/svg"><path d="M5.8 14.05l6.253 8.61 6.252-8.61-6.254 3.807z" fill="#0288d1" stroke-width="4.553" stroke-linejoin="round"/><path d="M12.051 1.347L5.8 11.833l6.252 3.807 6.254-3.807z" fill="#0288d1" stroke-width="5.025" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 120 120" id="sonar" xmlns="http://www.w3.org/2000/svg"><style>.a,.b{fill:#fff}.b{stroke:#fff;stroke-miterlimit:10}</style><path d="M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422.426-.427.709-.853.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957c0-.995.141-1.991.425-2.986-4.265-2.702-8.53-3.838-14.787-3.838z" fill="#1e88e5" stroke-width="1.422"/></symbol><symbol viewBox="0 0 412 395" id="stylelint" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-white</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#cfd8dc" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 412 395" id="stylelint_light" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-black</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#546e7a" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 200.00001 200.00001" id="stylus" xmlns="http://www.w3.org/2000/svg"><path d="M126.814 155.9c14.64-17.51 16.362-35.595 5.024-69.18-7.177-21.24-19.09-37.602-10.334-50.807 9.329-14.065 29.135-.43 12.63 18.371l3.301 2.297c19.806 2.296 29.566-24.83 14.783-32.58C113.179 3.621 79.02 42.803 94.09 88.156c6.458 19.232 15.5 39.613 8.18 55.83-6.314 13.923-18.514 22.103-26.695 22.39-17.079.862-5.74-38.32 13.922-48.08 1.722-.861 4.162-2.01 1.866-4.88-24.256-2.727-38.464 8.468-46.645 24.112-23.825 45.497 45.21 62.29 82.095 18.371z" fill="#c0ca33" stroke-width="1.435"/></symbol><symbol viewBox="0 0 24 24" id="swc" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jba"><stop offset="0" stop-color="#791223"/><stop offset="1" stop-color="#d92f3c"/></linearGradient><linearGradient xlink:href="#jba" id="jbb" x1="12.356" y1="21.559" x2="12.356" y2="2.949" gradientUnits="userSpaceOnUse"/></defs><path d="M6 3c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6 3 6.5V19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.5c0-.5-.17-.93-.46-1.27l-1.39-1.68C18.88 3.21 18.47 3 18 3H6zm-.07 1h12l.94 1H5.12l.81-1z" fill="url(#jbb)"/><path style="line-height:125%" d="M11.053 11.918h-.008c-.244.022-.475.054-.676.11a2.9 2.9 0 0 0-.856.412 3.399 3.399 0 0 0-.67.683 9.36 9.36 0 0 0-.586.95c-.07.131-.134.244-.201.365v.001h-.002l-.768 1.372-.003-.001c-.136.253-.264.485-.38.686-.123.212-.26.39-.411.539a1.599 1.599 0 0 1-.52.34c-.04.016-.092.024-.138.036h-.567v1.383H5.834v-.001c.245-.02.477-.053.679-.11a2.9 2.9 0 0 0 .856-.411c.245-.185.469-.413.67-.683.195-.275.39-.591.585-.95.07-.131.135-.244.202-.366l.004.001.002-.002.02-.038H10.948v-1.378h-.19v-.001H9.624c.125-.234.246-.452.355-.64.123-.21.259-.39.41-.538.152-.148.325-.26.52-.34.04-.015.091-.024.136-.035h.57V13.3h-.002v-1.381h-.56v-.001z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol viewBox="0 0 24 24" id="swift" xmlns="http://www.w3.org/2000/svg"><path d="M17.09 19.72c-2.36 1.36-5.59 1.5-8.86.1A13.807 13.807 0 0 1 2 14.5c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17z" fill="#fe5e2f"/></symbol><symbol viewBox="0 0 24 24" id="table" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5m4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1v-2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 200 200" id="terraform" xmlns="http://www.w3.org/2000/svg"><g transform="translate(177.03 -58.705) scale(.92881)" fill="#5c6bc0" stroke="#b0aff5" stroke-linejoin="round"><g stroke-width=".288"><path transform="skewY(26.439) scale(.89541 1)" d="M-203.8 170.95h64.714v51.88H-203.8zM-124.37 171.04h64.714v51.88h-64.714zM-124.37 236.09h64.714v51.88h-64.714z"/></g><path transform="skewY(-22.59) scale(-.92328 1)" stroke-width=".284" d="M-19.172 128.27h62.76v51.88h-62.76z"/></g></symbol><symbol viewBox="0 0 24 24" id="test-js" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="test-jsx" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="test-ts" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tex" xmlns="http://www.w3.org/2000/svg"><g font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-linejoin="miter"><text style="line-height:125%" x="9.914" y="364.919"><tspan x="9.914" y="364.919" font-size="287.5">T</tspan></text><text style="line-height:125%" x="136.374" y="435.558"><tspan x="136.374" y="435.558" font-size="287.5">E</tspan></text><text style="line-height:125%" x="307.819" y="361.201"><tspan x="307.819" y="361.201" font-size="287.5">X</tspan></text></g></symbol><symbol viewBox="0 0 24 24" id="todo" xmlns="http://www.w3.org/2000/svg"><path d="M3 5h6v6H3V5m2 2v2h2V7H5m6 0h10v2H11V7m0 8h10v2H11v-2m-6 5l-3.5-3.5 1.41-1.41L5 17.17l4.59-4.58L11 14l-6 6z" fill="#42a5f5"/></symbol><symbol id="travis" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style id="jkstyle2">.jkst0{fill:#cb3349}.jkst1{fill:#f4edae}.jkst2{fill:#e6ccad}.jkst3{fill:#656c67}.jkst4{fill:#e5caa3}.jkst5{fill:#c7b39a}.jkst6{fill:#ebd599}.jkst7{fill:#2d3136}.jkst8{fill:#edf6fa}.jkst9{opacity:.8}.jkst10{opacity:.75;fill:#ebd599}</style><g id="jkg99" transform="translate(11.017 12.484) scale(.8858)"><g id="jkg10"><path class="jkst0" d="M47.781 86.572s-31.118 21.903-32.335 30.247l2.335-.48S55.045 91.64 84.584 88.628l.669-3.749z" id="jkpath4" fill="#cb3349"/><path class="jkst0" d="M96.629 83.442l-24.511 17.385 1.325 1.063c.999-.806 43.539-13.798 43.539-13.798l8.969-5.623c-6.018.749-29.322.973-29.322.973z" id="jkpath6" fill="#cb3349"/><path class="jkst0" d="M117.932 104.469c17.405 0 43.495-17.046 43.495-17.046l-8.434-1.605c-.417.417-13.6-.462-13.6-.462l-6.258-1.738-14.951 17.036-1.217 2.956c1.075-.437.965.859.965.859z" id="jkpath8" fill="#cb3349"/></g><path class="jkst0" d="M174.728 158.832l-5.377 1.514-24.843-.537-15.541-12.085-18.784 4.7-21.726-1.88-12.166 13.294-22.828 6.819-11.398-3.534-.574-.494 5.116 12.527s11.588 12.424 18.061 13.885c6.472 1.461 18.165-.105 26.935-1.463 8.769-1.357 15.764-4.489 18.582-9.603 2.818-5.117 3.236-6.578 3.236-6.578s8.353 11.797 15.556 13.155c7.203 1.357 28.605-5.952 28.605-5.952s13.051-3.549 15.346-8.038c2.297-4.489 8.353-19.209 8.353-19.209zM44.456 169.038l-.361-.166-2.013-1.736z" id="jkpath12" fill="#cb3349"/><g id="jkg97"><path class="jkst1" d="M195.832 70.085a48.125 48.125 0 0 0-.21-2.009 26.472 26.472 0 0 0-.215-1.424c-1.793-1.509-3.831-2.851-5.952-4.071-2.299-1.343-4.704-2.546-7.159-3.663-2.438-1.15-4.942-2.191-7.461-3.207a134.313 134.313 0 0 0-3.798-1.477c-1.269-.495-2.55-.956-3.835-1.424 2.697.447 5.366 1.059 8.015 1.741 1.723.446 3.437.945 5.14 1.477-12.112-31.655-41.07-52.27-72.687-52.27-31.622 0-60.577 20.615-72.686 52.27a109.044 109.044 0 0 1 5.137-1.477c2.653-.682 5.323-1.294 8.018-1.741-1.289.468-2.567.929-3.84 1.424-1.267.472-2.536.967-3.798 1.477-2.519 1.016-5.016 2.057-7.46 3.207-2.45 1.117-4.857 2.32-7.156 3.663-2.121 1.219-4.157 2.562-5.957 4.071-.075.457-.151.951-.21 1.424a51.768 51.768 0 0 0-.21 2.009 51.354 51.354 0 0 0-.177 4.061 59.216 59.216 0 0 0 .5 8.11c.37 2.692.864 5.366 1.595 7.951.36 1.295.768 2.572 1.24 3.808.237.617.495 1.225.764 1.816.134.294.274.585.413.864l.172.328c.199.101.408.204.607.3l1.204.575c.671.305 1.6.746 2.368 1.09.043-.037.086-.075.123-.114l-2.235-8.513c.474-.13 4.718-1.225 12.032-2.617a38.816 38.816 0 0 1-1.772-.381c-1.665-.414-3.309-.919-4.899-1.564a22.415 22.415 0 0 1-2.309-1.115c-.742-.426-1.472-.908-2.037-1.548 8.036 2.622 24.64 1.434 39.399-.091 13.499-1.391 27.029-2.293 40.63-2.32 13.602.027 27.137.929 40.63 2.32 14.766 1.525 31.37 2.713 39.405.091-.564.64-1.293 1.123-2.035 1.548a22.5 22.5 0 0 1-2.308 1.115c-1.592.645-3.234 1.15-4.899 1.564-.247.059-.496.113-.743.166 8.02 1.488 12.689 2.697 13.188 2.831l-2.138 8.11c.43-.194.864-.381 1.29-.574l1.202-.575c.2-.097.403-.199.607-.3l.166-.328c.146-.279.286-.57.419-.864.27-.591.528-1.199.764-1.816a42.235 42.235 0 0 0 1.241-3.808c.731-2.585 1.225-5.259 1.595-7.951.345-2.685.526-5.398.501-8.11a50.874 50.874 0 0 0-.179-4.059z" id="jkpath14" fill="#f4edae"/><path class="jkst2" d="M116.787 182.661c-1.064.16-2.128.295-3.186.375-.682.033-1.404.102-2.059.102l-.242.005c.822-1.837 1.446-3.26 1.919-4.339.963 1.08 2.188 2.417 3.568 3.857z" id="jkpath16" fill="#e6ccad"/><path class="jkst2" d="M119.101 185.018c3.304 3.272 7.398 5.146 11.904 5.479-7.569 3.074-14.702 4.26-20.197 4.63-5.478.367-11.032-.279-16.474-1.771.456-.082.79-.14 1.193-.189.447-.054 10.206-1.327 14.605-7.868l.413.009 1.08-.009c.731 0 1.395-.06 2.094-.087a43.69 43.69 0 0 0 4.878-.703c.167.171.333.338.504.509z" id="jkpath18" fill="#e6ccad"/><path class="jkst3" d="M128.464 87.071a98.82 98.82 0 0 1-1.048 1.343c-1.933 2.444-4.614 5.57-7.794 8.627a369.585 369.585 0 0 0-11.404-.177c-6.46 0-12.655.171-18.537.457 8.311-3.449 18.296-6.818 29.109-8.842a113.323 113.323 0 0 1 9.674-1.408z" id="jkpath20" fill="#656c67"/><path class="jkst3" d="M79.821 90.792c-2.966 2.084-6.317 4.744-9.566 7.971a360.155 360.155 0 0 0-21.567 2.81c9.207-4.232 19.713-8.127 31.133-10.781z" id="jkpath22" fill="#656c67"/><path class="jkst3" d="M181.48 107.969l-3.384 23.679-16.212 11.355-42.283-4.807-6.365-20.961a1.383 1.383 0 0 0-1.108-.971c-1.567-.253-2.953-.382-4.108-.382-1.16 0-2.541.129-4.115.382-.522.086-.95.461-1.106.971l-6.209 20.45-42.047 9.357-16.662-11.672-3.283-26.572c.715-.404 1.441-.806 2.176-1.209 1.031-.222 2.191-.457 3.475-.704l3.094 25.073c.048.392.264.741.586.967l11.462 8.032a1.425 1.425 0 0 0 1.101.213l34.57-7.692c.119-.027.237-.069.344-.124a1.39 1.39 0 0 0 .682-.827l6.225-20.498c1.67-.43 5.947-1.429 9.706-1.429 3.749 0 8.03.999 9.701 1.429l6.225 20.498c.161.532.624.912 1.176.977l34.57 3.927c.335.037.677-.05.952-.242l11.469-8.025c.31-.22.52-.566.573-.946l3.062-21.421c2.301.444 4.224.846 5.733 1.172z" id="jkpath24" fill="#656c67"/><path class="jkst3" d="M185.751 93.119l-2.976 11.29c-6.086-1.342-19.456-3.975-37.654-5.747 5.946-2.535 12-5.715 17.531-9.69 10.829 1.53 18.78 3.169 23.099 4.147z" id="jkpath26" fill="#656c67"/><g id="jkg32"><path class="jkst4" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath28" fill="#e5caa3"/><path class="jkst4" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath30" fill="#e5caa3"/></g><g id="jkg38"><path class="jkst5" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath34" fill="#c7b39a"/><path class="jkst5" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath36" fill="#c7b39a"/></g><path class="jkst2" d="M187.481 115.502c.508.419.911 1.504.456 6.558-.559 6.188-3.16 17.049-4.771 18.8-1.778.344-5.505-.064-7.778-.595.393-1.559.505-2.306.822-3.9l3.975-2.781c.317-.22.526-.566.58-.941l2.778-19.466c1.686.912 3.421 1.899 3.938 2.325z" id="jkpath40" fill="#e6ccad"/><path class="jkst2" d="M40.937 140.908c.199.704.408 1.407.624 2.1-2.139.628-6.495 1.23-8.465.886-1.633-1.645-4.679-12.966-5.345-18.978-.543-4.871-.162-5.924.333-6.334.575-.483 2.728-1.708 4.593-2.707l2.519 20.449c.048.393.257.741.586.967z" id="jkpath42" fill="#e6ccad"/><path class="jkst2" d="M121.347 141.194l-.151 1.305s-4.581 4.248-11.956 5.199c-7.375.95-13.171-3.582-13.171-3.582.242.788.586 2.567 2.256 4.086a53.184 53.184 0 0 0-6.313-.393c-.804 0-1.616.023-2.401.061-4.539.237-10.924 7.1-15.414 14.014-2.203.697-9.089 2.883-17.06 5.237-7.44-10.309-11.098-20.842-11.469-21.932l.005-.006c-.15-.419-.301-.839-.441-1.268l1.913 1.338v.005l4.726 3.309 1.58 1.101c.236.167.515.253.794.253.102 0 .204-.011.305-.031l43.435-9.67a1.385 1.385 0 0 0 1.025-.95l6.194-20.39c1.069-.145 2.008-.22 2.814-.22.801 0 1.746.075 2.815.22l6.374 20.997c.162.532.624.919 1.171.977z" id="jkpath44" fill="#e6ccad"/><path class="jkst2" d="M170.926 140.066l1.402-.984c-.232.973-.484 1.94-.747 2.896-1.949 6.248-4.25 11.774-6.805 16.656-.565.039-1.161.061-1.8.061-1.972 0-3.986-.167-6.215-.371-3.868-.355-10.007-1.058-11.946-1.283-1.67-1.332-7.385-5.873-12.14-9.615-.187-.151-.348-.291-.505-.42-.837-.708-1.789-1.513-3.717-1.513-1.751 0-4.308.638-10.489 2.508 3.212-2.401 3.233-5.5 3.233-5.5l.151-1.305 40.748 4.629a1.41 1.41 0 0 0 .955-.241l4.094-2.868z" id="jkpath46" fill="#e6ccad"/><path class="jkst6" d="M140.937 54.337c.124 3.625.033 10.194-1.655 16.345a1.335 1.335 0 0 0 0 .704 259.298 259.298 0 0 0-6.446-.591c2.412-5.054 2.938-10.436 3.052-12.332 1.852-1.317 3.696-2.896 5.049-4.126z" id="jkpath48" fill="#ebd599"/><path class="jkst6" d="M79.456 58.462c.112 1.896.638 7.267 3.046 12.317-2.149.171-4.297.37-6.441.596a1.328 1.328 0 0 0 0-.694c-1.686-6.139-1.772-12.714-1.654-16.345 1.353 1.231 3.19 2.81 5.049 4.126z" id="jkpath50" fill="#ebd599"/><path class="jkst7" d="M151.835 125.675c-2.89-1.396-6.059.377-11.828.484-4.292.151-7.896.198-8.132-6.543-.237-6.747 2.513-12.326 6.805-12.478 4.292-.15 8.207 5.2 8.735 11.931.145 1.854-.06 3.207-.521 4.21 3.996-.477 4.899 2.235 4.941 2.396zm-13.488-9.878a2.203 2.203 0 0 0 2.154-2.235 2.186 2.186 0 0 0-2.235-2.153 2.194 2.194 0 0 0 .081 4.388z" id="jkpath52" fill="#2d3136"/><circle transform="rotate(-1.049 138.093 113.428)" class="jkst8" cx="138.307" cy="113.602" id="jkellipse54" r="2.194" fill="#edf6fa"/><path class="jkst7" d="M83.484 120.953c.063 6.747-3.509 6.339-7.806 6.381-.435.011-.848.016-1.258.022-.482.011-.944.016-1.39.005-4.168-.005-6.833-.194-9.19 1.079.058-.145 1.09-2.461 4.835-3.4-.414-.914-.673-2.181-.742-3.937-.257-6.741 3.9-12.269 8.197-12.306 4.292-.042 7.289 5.411 7.354 12.156zm-6.634-3.529a2.195 2.195 0 1 0-.122-4.388 2.195 2.195 0 0 0 .122 4.388z" id="jkpath56" fill="#2d3136"/><circle transform="rotate(-1.473 76.78 115.216)" class="jkst8" cx="76.79" cy="115.23" id="jkellipse58" r="2.195" fill="#edf6fa"/><g class="jkst9" id="jkg64" opacity=".8"><path class="jkst6" d="M50.691 75.155s.667-8.692 2.03-12.023c.702-1.717 4.996-2.81 8.276-3.591 3.278-.78 8.508-2.342 9.524 2.264 1.015 4.606 2.653 7.963 3.746 9.446l-1.404-18.97-22.562 5.464-1.484 16.786.703 1.327 1.171-.703" id="jkpath60" fill="#ebd599"/><path class="jkst6" d="M164.855 75.155s-.666-8.692-2.029-12.023c-.703-1.717-4.997-2.81-8.275-3.591-3.28-.78-8.51-2.342-9.526 2.264-1.013 4.606-2.654 7.963-3.748 9.446l1.407-18.97 22.562 5.464 1.483 16.786-.703 1.327-1.171-.703" id="jkpath62" fill="#ebd599"/></g><path class="jkst10" d="M132.965 18.378s-.598 45.49-11.224 45.49h-14.875-12.752c-10.626 0-11.484-45.47-11.484-45.47l-5.22 15.438.085 21.183 3.707 2.947 1.685 9.096 2.357 5.307 45.482.084 2.105-3.791 1.769-6.4.254-4.043 5.023-14.341z" id="jkpath66" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M166.429 60.794s2.187 15.692 7.974 18.522c5.788 2.829 0 0 0 0l-8.103-2.444z" id="jkpath68" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M48.908 60.794s-2.187 15.692-7.975 18.522c-5.788 2.829 0 0 0 0l8.104-2.444z" id="jkpath70" opacity=".75" fill="#ebd599"/><path class="jkst7" d="M167.987 76.8c2.755.902 5.526 1.858 8.036 3.325-1.343-.532-2.729-.913-4.126-1.257a70.385 70.385 0 0 0-4.201-.924c-2.82-.531-5.65-.982-8.498-1.327-2.841-.37-5.687-.682-8.546-.924-2.858-.241-5.709-.483-8.573-.65-11.446-.704-22.924-.88-34.41-.892-11.483.006-22.962.221-34.409.897-2.862.166-5.715.409-8.572.651-2.857.241-5.71.548-8.546.923-2.847.345-5.678.796-8.498 1.327-1.407.264-2.81.57-4.206.919-1.391.344-2.783.725-4.126 1.257 2.509-1.466 5.28-2.427 8.041-3.331.232-.075.467-.139.703-.214-.015-.059-.032-.113-.043-.177-.048-.317-1.069-7.859.709-18.645.086-.516.456-.935.962-1.075l2.917-.831c.634-22.625 9.952-33.266 10.243-33.594-8.326 13.397-8.25 29.286-8.106 32.986l18.128-5.152c.016-.005.026-.005.042-.01.076-.016.151-.027.226-.032.021 0 .049-.006.075-.006a1.19 1.19 0 0 1 .297.027c.015 0 .031.011.053.016.075.016.145.042.224.075.033.016.054.033.086.049.058.033.119.07.177.112.016.011.034.016.049.033l.032.032c.016.016.037.027.054.044.012.016.494.493 1.262 1.209-.182-5.973.102-23.108 8.262-37.31-.172.498-6.646 19.428-4.415 40.645.724.58 1.486 1.149 2.229 1.649.359.247.58.655.585 1.09.006.07.161 6.833 3.148 12.586.042.086.074.177.102.268 7.429-.505 14.878-.709 22.312-.714 7.436.005 14.88.22 22.307.731.027-.097.06-.193.109-.285 2.986-5.753 3.142-12.516 3.142-12.586.01-.436.231-.843.591-1.09.741-.5 1.493-1.069 2.224-1.649 2.234-21.217-4.24-40.147-4.411-40.645 8.153 14.201 8.444 31.336 8.262 37.31a62.536 62.536 0 0 0 1.261-1.209c.016-.016.039-.027.053-.044.012-.01.018-.021.033-.032.016-.016.033-.022.049-.033.06-.042.119-.079.177-.118.028-.01.054-.027.081-.043.081-.033.155-.059.236-.08.016 0 .033-.011.049-.011.096-.021.2-.032.296-.027.027 0 .049.006.07.006.075.005.156.016.231.032.012.006.028.006.042.01l18.129 5.152c.146-3.7.221-19.59-8.104-32.986.289.328 9.609 10.969 10.237 33.594l2.922.831c.499.14.875.559.962 1.075 1.777 10.786.752 18.328.708 18.645-.01.065-.026.124-.042.182.239.07.47.139.707.215zm-3.297-.968c.14-1.207.789-7.809-.591-16.801l-20.52-5.833c.184 3.475.265 11.012-1.707 18.199a1.619 1.619 0 0 1-.101.258c.203.021.408.037.606.064 5.769.661 11.511 1.584 17.189 2.83 1.712.398 3.426.823 5.124 1.283zm-25.409-5.151c1.688-6.15 1.779-12.72 1.655-16.345-1.353 1.23-3.197 2.809-5.049 4.125-.114 1.896-.64 7.278-3.052 12.332 2.149.173 4.298.366 6.446.591a1.33 1.33 0 0 1 0-.703zm-56.78.098c-2.408-5.05-2.934-10.422-3.046-12.317-1.858-1.316-3.696-2.895-5.049-4.125-.119 3.631-.032 10.206 1.654 16.345.065.237.058.473 0 .694 2.145-.227 4.292-.425 6.441-.597zm-8.933.864a1.65 1.65 0 0 1-.098-.247c-1.975-7.187-1.889-14.723-1.712-18.199L51.244 59.03c-1.38 8.982-.736 15.583-.597 16.797 1.703-.462 3.411-.887 5.131-1.284 2.835-.628 5.693-1.154 8.556-1.638 2.869-.478 5.747-.843 8.626-1.192.205-.027.404-.042.608-.07z" id="jkpath72" fill="#2d3136"/><g id="jkXMLID_1_"><g id="jkg78"><path class="jkst7" d="M129.293 18.973v17.025h-12.068v-4.974h-2.72v22.981h4.109v12.85H97.505v-12.85h4.092v-22.98h-2.711v4.974h-12.06V18.973zm-3.626 13.408v-9.789H90.443v9.789h4.816v-4.974h9.964v30.225h-4.1v5.606h13.865v-5.606h-4.1V27.407h9.964v4.974z" id="jkpath74" fill="#2d3136"/><path class="jkst0" id="jkpolygon76" fill="#cb3349" d="M101.123 57.632h4.1V27.407h-9.964v4.974h-4.816v-9.79h35.224v9.79h-4.816v-4.974h-9.964v30.225h4.1v5.606h-13.864z"/></g></g><path class="jkst3" d="M30.694 93.119c1.759-.399 4.136-.907 7.051-1.47a104.37 104.37 0 0 0-6.222 4.597z" id="jkpath83" fill="#656c67"/><path class="jkst5" d="M95.111 139.78s.492 3.165-3.938 4.519c-4.428 1.355-32.482 9.716-35.682 9.263-3.199-.451-11.319-5.874-11.319-5.874l-1.969-7.004 12.016 7.492z" id="jkpath85" fill="#c7b39a"/><path class="jkst5" d="M120.242 139.167s-.354 3.182 4.131 4.345c4.484 1.161 32.875 8.295 36.05 7.704 3.176-.591 11.053-6.361 11.053-6.361l1.663-7.084-11.045 6.588z" id="jkpath87" fill="#c7b39a"/><path class="jkst5" d="M28.412 133.956s3.887 7.775 10.166 5.083l4.485 1.645-.448 3.29-9.419 1.195-2.541-1.494z" id="jkpath89" fill="#c7b39a"/><path class="jkst5" d="M187.551 131.822s-6.353 8.115-12.632 5.424l-2.019 1.302.448 3.289 9.419 1.196 2.54-1.495z" id="jkpath91" fill="#c7b39a"/><path class="jkst5" d="M89.279 192.904s23.03 11.611 49.106-4.188l-8.374-.571s-18.272 7.232-32.738 3.235z" id="jkpath93" fill="#c7b39a"/><path class="jkst7" d="M112.626 171.509l1.594 1.899c.036.046 3.577 4.26 7.906 8.552 2.879 2.853 6.357 4.297 10.343 4.297 1.361 0 2.791-.175 4.235-.523 1.34-.326 2.796-.673 4.287-1.03 5.384-1.287 11.482-2.749 14.438-3.577.585-.166 1.238-.315 1.925-.472 3.935-.909 9.329-2.163 12.187-7.889 2.149-4.297 5.047-9.874 7.197-13.961-1.863.859-3.816 1.79-5.203 2.52-2.138 1.123-4.938 1.667-8.558 1.667-2.152 0-4.266-.181-6.605-.389-4.675-.43-12.586-1.361-12.667-1.372l-.606-.067-.478-.383c-.071-.052-7.003-5.575-12.606-9.981-.227-.186-.434-.358-.621-.513-.59-.503-.59-.503-.942-.503-1.797 0-7.02 1.62-18.462 5.167l-.703.223-.689-.26c-.078-.026-7.585-2.81-16.581-2.81-.736 0-1.47.019-2.185.056-.901.046-5.958 2.448-12.425 12.68l-.419.657-.741.238c-.107.037-11.238 3.63-23.042 7.005l-.766.218-.725-.337c-.077-.031-4.696-2.174-9.091-4.194 2.397 3.541 5.462 7.958 8.159 11.422 4.711 6.067 10.649 11.674 22.034 11.674 1.428 0 2.945-.088 4.503-.265 11.581-1.309 14.563-1.837 16.168-2.117.543-.092.973-.171 1.522-.238.088-.011 9.571-1.237 12.232-7.206 2.744-6.134 3.298-7.595 3.319-7.651l.968-2.583s.12-.669.317-.877c0 .005 0 .005.005.005l.019.016c.305.219.757.902.757.902zM40.499 55.71c-2.516 1.014-5.016 2.06-7.46 3.209-2.449 1.119-4.856 2.32-7.155 3.66-2.121 1.222-4.157 2.563-5.954 4.076-.077.455-.149.952-.211 1.423a51.357 51.357 0 0 0-.388 6.068c-.026 2.713.16 5.426.502 8.112.372 2.692.864 5.369 1.594 7.952a41.963 41.963 0 0 0 1.243 3.804c.233.623.492 1.228.762 1.818.134.294.274.585.413.864l.172.326c.201.104.409.207.605.3l1.206.574c.673.311 1.6.751 2.366 1.093.046-.037.088-.078.124-.114l-2.231-8.511c.471-.129 4.717-1.227 12.032-2.619a33.744 33.744 0 0 1-1.775-.379 36.704 36.704 0 0 1-4.898-1.563 22.857 22.857 0 0 1-2.309-1.119c-.741-.425-1.471-.905-2.035-1.547 8.035 2.624 24.637 1.433 39.398-.088 13.501-1.393 27.028-2.293 40.628-2.325 13.6.031 27.138.931 40.63 2.325 14.77 1.522 31.374 2.713 39.406.088-.564.642-1.293 1.122-2.034 1.547-.739.42-1.522.782-2.309 1.119a36.965 36.965 0 0 1-4.903 1.563c-.244.056-.492.114-.741.166 8.02 1.486 12.689 2.697 13.186 2.832l-2.138 8.107c.43-.192.864-.377 1.288-.574l1.207-.574c.196-.094.404-.196.606-.3l.166-.326c.144-.279.284-.57.419-.864.27-.591.528-1.196.767-1.818.471-1.231.879-2.51 1.236-3.804.731-2.583 1.228-5.26 1.595-7.952.346-2.686.528-5.4.502-8.112a52.755 52.755 0 0 0-.176-4.059 51.573 51.573 0 0 0-.213-2.009 29.83 29.83 0 0 0-.213-1.423c-1.797-1.513-3.831-2.853-5.954-4.076-2.299-1.34-4.704-2.541-7.159-3.66-2.438-1.149-4.943-2.195-7.46-3.209a140.105 140.105 0 0 0-3.801-1.476c-1.267-.491-2.552-.956-3.835-1.423 2.696.445 5.369 1.06 8.013 1.739 1.724.446 3.444.948 5.141 1.481-12.11-31.658-41.07-52.272-72.685-52.272-31.622 0-60.576 20.614-72.684 52.272a107.832 107.832 0 0 1 5.135-1.481c2.651-.678 5.322-1.294 8.02-1.739-1.29.466-2.568.931-3.842 1.423-1.268.47-2.535.967-3.799 1.475zm159.43 18.316a53.972 53.972 0 0 1-.258 8.733 55.462 55.462 0 0 1-1.619 8.605c-.4 1.414-.86 2.811-1.404 4.198a38.295 38.295 0 0 1-.89 2.071c-.161.341-.331.678-.523 1.025l-.284.512a8.975 8.975 0 0 1-.348.574l-.294.457-.461.237c-.492.254-.895.445-1.342.653l-1.298.585a88.22 88.22 0 0 1-2.62 1.065c-.611.239-1.15.457-1.662.674l-1.444 5.487c-.036-.009-.471-.12-1.283-.315l-.078.574c1.594.833 4.726 2.522 5.793 3.403 2.148 1.775 2.299 4.587 1.823 9.841-.244 2.697-1.139 7.946-2.381 12.767-2.144 8.298-3.283 9.273-4.753 9.649-.746.192-1.894.383-3.008.383-2.266 0-5.353.063-7.429-.439-.533 1.888-2.055 6.812-5.068 12.962.151-.073.3-.135.435-.207 3.717-1.952 10.861-5.064 11.162-5.199l5.643-2.452-2.89 5.435c-.067.118-6.264 11.773-10.059 19.383-3.769 7.538-10.835 9.179-15.065 10.151-.637.151-1.241.291-1.733.425-3.035.854-9.18 2.319-14.599 3.623-.064.016-.13.033-.197.042a64.057 64.057 0 0 1-10.955 5.411c-14.568 5.518-29.923 5.208-43.844.092a647.05 647.05 0 0 1-9.193 1.097 45.12 45.12 0 0 1-4.985.291c-13.264 0-20.294-6.736-25.425-13.331-5.493-7.062-12.212-17.546-12.497-17.985L31 158.426l6.585 2.961c3.152 1.419 12.524 5.757 15.205 7 .217-.061.43-.124.642-.186-4.457-6.357-8.112-13.605-10.695-21.634-2.195.662-5.576 1.175-8.206 1.175-.961 0-1.822-.072-2.484-.228-1.471-.336-3.148-1.754-5.431-9.795-1.325-4.668-2.314-9.764-2.603-12.387-.57-5.121-.466-7.864 1.662-9.636 1.283-1.071 5.611-3.344 6.507-3.809l-.192-1.58c-13.75 8.08-21.991 15.22-22.157 15.366L0 134.302l7.005-11.047c5.544-8.755 11.948-15.832 17.84-21.284-.244-.098-.471-.196-.71-.294l-1.299-.585a34.907 34.907 0 0 1-1.34-.653l-.461-.237-.295-.457c-.166-.249-.238-.388-.347-.574l-.29-.512c-.181-.347-.358-.684-.518-1.025a30.878 30.878 0 0 1-.89-2.071 44.74 44.74 0 0 1-1.404-4.198 54.745 54.745 0 0 1-1.62-8.605 54.664 54.664 0 0 1-.259-8.733c.078-1.455.218-2.909.419-4.354.104-.725.213-1.45.358-2.17.15-.734.296-1.418.518-2.221l.155-.564.404-.317c2.294-1.802 4.768-3.163 7.284-4.369a78.87 78.87 0 0 1 6.311-2.616c5.943-16.493 16.162-31.118 29.591-41.311C74.337 5.57 90.664 0 107.671 0s33.334 5.57 47.218 16.106c13.43 10.193 23.649 24.819 29.588 41.307a78.282 78.282 0 0 1 6.316 2.62c2.515 1.206 4.99 2.567 7.283 4.369l.404.317.156.564c.227.803.372 1.487.517 2.221.146.72.26 1.445.357 2.17.203 1.443.348 2.897.419 4.352zm-11.995 48.031c.456-5.052.058-6.139-.455-6.554-.513-.43-2.247-1.412-3.935-2.329l-2.779 19.464a1.39 1.39 0 0 1-.58.942l-3.977 2.781c-.315 1.593-.429 2.345-.817 3.903 2.273.528 5.999.938 7.775.595 1.612-1.748 4.214-12.61 4.768-18.802zm-5.161-17.648l2.977-11.29c-4.318-.978-12.27-2.615-23.1-4.148-5.53 3.976-11.582 7.155-17.53 9.691 18.199 1.771 31.57 4.406 37.653 5.747zm-4.68 27.237l3.385-23.676a240.127 240.127 0 0 0-5.731-1.169l-3.059 21.422a1.415 1.415 0 0 1-.575.943l-11.472 8.023c-.27.192-.616.28-.947.243l-34.572-3.929a1.391 1.391 0 0 1-1.176-.973l-6.227-20.5c-1.668-.431-5.949-1.43-9.696-1.43-3.764 0-8.041.999-9.708 1.43l-6.228 20.5a1.388 1.388 0 0 1-1.025.947l-34.572 7.692a1.483 1.483 0 0 1-.306.033 1.36 1.36 0 0 1-.792-.25l-11.467-8.029a1.396 1.396 0 0 1-.585-.968l-3.091-25.072c-1.284.249-2.443.487-3.479.703-.734.405-1.46.809-2.174 1.213l3.281 26.568 16.666 11.675 42.047-9.354 6.207-20.449a1.389 1.389 0 0 1 1.108-.975c1.574-.253 2.95-.382 4.116-.382 1.153 0 2.536.129 4.105.382.528.083.957.461 1.108.975l6.366 20.956 42.282 4.808zm-8.07-4.411l2.992-20.948c-8.439-1.536-20.78-3.394-35.897-4.554-13.647 4.707-25.077 6.108-25.766 6.155l-.797.057c4.353.374 8.454 1.544 8.66 1.605.452.135.804.481.944.933l6.186 20.366 33.138 3.764zm2.303 11.845l-1.404.983-3.779 2.651-4.095 2.868c-.279.192-.621.28-.954.243l-40.746-4.633-2.966-.337a1.39 1.39 0 0 1-1.171-.977l-6.377-20.998c-1.066-.145-2.014-.219-2.81-.219-.809 0-1.751.073-2.817.219l-6.192 20.392a1.383 1.383 0 0 1-1.025.946l-43.435 9.672c-.103.02-.206.03-.305.03-.279 0-.559-.083-.798-.253l-1.578-1.098-4.726-3.307v-.011l-1.91-1.335c.135.43.289.85.441 1.268l-.006.006c.368 1.092 4.028 11.622 11.467 21.929a873.96 873.96 0 0 0 17.057-5.234c4.488-6.917 10.877-13.777 15.418-14.014a51.12 51.12 0 0 1 2.402-.061c2.221 0 4.344.16 6.31.393-1.671-1.517-2.013-3.298-2.256-4.085 0 0 5.793 4.53 13.17 3.584 7.378-.953 11.959-5.204 11.959-5.204s-.021 3.102-3.236 5.503c6.182-1.869 8.739-2.511 10.489-2.511 1.931 0 2.883.808 3.717 1.519.161.129.322.268.507.419a3519.302 3519.302 0 0 1 12.141 9.614c1.936.227 8.075.926 11.943 1.283 2.23.201 4.245.372 6.217.372.637 0 1.233-.026 1.797-.063 2.558-4.88 4.857-10.411 6.808-16.653.261-.96.516-1.928.743-2.901zm-15.034-51.593c-.01-.006-.02-.012-.031-.012a551.624 551.624 0 0 0-9.826-.651 905.6 905.6 0 0 0-13.667-.668 72.95 72.95 0 0 1-1.574 2.225c-2.479 3.355-7.398 9.51-13.704 14.729 8.926-1.6 24.409-5.56 37.803-14.905.336-.238.668-.486.999-.718zm-29.876.926c.377-.471.729-.926 1.044-1.34-3.281.331-6.512.808-9.67 1.408-10.814 2.024-20.801 5.389-29.11 8.837a383.259 383.259 0 0 1 18.54-.455c3.908 0 7.708.067 11.404.176 3.179-3.056 5.861-6.182 7.792-8.626zm3.587 102.085c-4.503-.332-8.598-2.205-11.903-5.477a271.86 271.86 0 0 0-.502-.512 44.25 44.25 0 0 1-4.881.704c-.698.026-1.361.087-2.091.087l-1.083.011-.413-.011c-4.396 6.539-14.159 7.813-14.605 7.87-.403.046-.734.103-1.191.186 5.442 1.491 10.996 2.138 16.474 1.77 5.492-.367 12.627-1.558 20.195-4.628zm-17.4-7.461a45.604 45.604 0 0 0 3.184-.378 138.958 138.958 0 0 1-3.568-3.857 398.441 398.441 0 0 1-1.92 4.339h.243c.658.001 1.378-.071 2.061-.104zm-3.354-78.632c1.827-1.103 3.582-2.366 5.249-3.712a422.33 422.33 0 0 0-7.278-.072c-10.137 0-19.606.415-28.189 1.061-8.61 4.209-13.875 7.672-13.998 7.76l-8.268 5.514 5.679-8.149a52.452 52.452 0 0 1 2.956-3.857c-9.536 1.066-17.477 2.329-23.41 3.422l3.038 24.632 10.453 7.321 33.184-7.378 6.212-20.464c.104-.337.331-.621.627-.793.098-.063.202-.109.315-.14.192-.052 3.51-.999 7.336-1.465zm3.816-18.788c-2.31-.036-4.623-.057-6.933-.062h-.005c-3.39.005-6.787.041-10.189.109l-6.269 2.971c-.005.005-.041.021-.088.048-.942.46-9.174 4.613-16.919 12.021 6.943-3.65 17.146-8.418 29.153-12.115a144.186 144.186 0 0 1 11.25-2.972zM70.251 98.761c3.251-3.225 6.605-5.886 9.567-7.967-11.415 2.651-21.923 6.543-31.128 10.778a360.846 360.846 0 0 1 21.561-2.811zm2.159-9.949a150.122 150.122 0 0 1 11.813-2.796c-5.798.212-11.6.481-17.393.808-3.366.186-6.715.414-10.065.667-1.678.129-3.345.263-5.007.445-.476.046-.942.098-1.418.16-4.369 2.614-21.127 13.134-32.631 26.889 11.179-7.769 30.654-19.443 54.701-26.173zm-30.85 54.197a68.861 68.861 0 0 1-.621-2.102l-5.162-3.612a1.391 1.391 0 0 1-.586-.969l-2.516-20.449c-1.864.999-4.017 2.225-4.592 2.707-.497.409-.875 1.46-.336 6.332.668 6.01 3.712 17.333 5.348 18.979 1.968.347 6.327-.258 8.465-.886zm-3.815-51.36a229.005 229.005 0 0 0-7.051 1.47l.829 3.127a103.93 103.93 0 0 1 6.222-4.597z" id="jkpath95" fill="#2d3136"/></g></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tune" xmlns="http://www.w3.org/2000/svg"><path d="M6.85 2.852h-2v6h2v-6m12 0h-2v10h2v-10m-16 10h2v8h2v-8h2v-2h-6v2m12-6h-2v-4h-2v4h-2v2h6v-2m-4 14h2v-10h-2v10m4-6v2h2v4h2v-4h2v-2h-6z" fill="#fbc02d" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 50 50" id="twig" xmlns="http://www.w3.org/2000/svg"><path d="M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537.132.27.282.487.332.482.123-.011.075-1.196-.1-2.454-.331-2.398-1.176-4.435-2.358-5.69-.2-.212-.344-.4-.319-.419.093-.067 1.327.843 1.842 1.359.293.293.735.825.981 1.181.328.474.465.618.51.534.078-.147-.21-9.903-.376-12.701-.074-1.255.063-1.023.61 1.035 1.064 4.006 1.858 7.922 2.342 11.55.086.637.173 1.172.195 1.19.022.016.092.001.157-.034.888-.483 1.524-.667 2.55-.736.727-.048.945.062.35.178-1.15.222-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.434.152.366.037.426-1.067.56-.716.088-.977.096-1.202.037-.356-.092-1.118-.098-1.195-.008-.031.036-.243.066-.47.066-.38 0-.423-.017-.535-.215zm1.974-3.233c.152-.205.072-.41-.204-.522-.225-.09-.263-.088-.437.025-.21.137-.252.43-.08.554.18.13.607.096.72-.057zm1.248.086a.763.763 0 0 0 .214-.203c.241-.33-.352-.622-.745-.366-.406.265.08.785.531.569zm2.288 3.094c-.033-.039.117-.387.334-.775.216-.387.411-.665.433-.618.07.152-.201 1.28-.33 1.372-.15.108-.354.117-.437.02zM8.2 47.092c-.29-.343-.221-.434.14-.182.176.123.321.263.321.31 0 .165-.279.087-.46-.128zm8.649-.145c0-.053.102-.18.227-.282.25-.204.312-.113.143.207-.095.18-.37.236-.37.075zm8.065-.827c-.243-.025-.48-.088-.527-.141-.11-.125-.114-3.043-.004-3.043.045 0 .132.149.193.331.127.38.228.42.31.124.094-.337.065-3.472-.039-4.297-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.436-4.047-1.711-.764-.15-.522-.224.598-.182 2.364.089 4.167.706 5.847 2.001a11.046 11.046 0 0 1 2.32 2.502c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.718 2.539-.988 4.02-.952.933.023 1.01.036 1.77.307a6.822 6.822 0 0 1 1.363.662c.612.407 1.309 1.004 1.235 1.058-.026.018-.343-.165-.705-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.651 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.449 1.612-.904 1.715-.83.022.016-.172.22-.432.454-1.957 1.754-3.248 3.76-4.232 6.572-.938 2.68-1.366 5.588-1.368 9.3-.002 1.741.188 4.385.366 5.101.125.505.08.546-.585.546-.55 0-2.306.138-3.416.27-.414.05-.817.04-1.609-.036-.58-.056-1.129-.119-1.218-.14-.165-.037-.18-.014-.2.302-.01.186-.098.203-.728.139zm2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.211.13.51.42.616.297.108.413.106.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.691-1.755-.307-1.428.404.121.266.299.35.738.354.227 0 .387-.045.516-.147zm3.011 6.681c-.027-.05.088-.268.256-.484.879-1.135 1.22-1.544 1.284-1.544.04 0 .056.037.036.082l-.423.964c-.212.485-.445.924-.519.977-.169.122-.57.125-.634.005zm2.446-.596c0-.121.853-.683.896-.59.018.04-.056.209-.166.376-.168.259-.238.305-.464.305-.164 0-.266-.035-.266-.091zm-13.04-.124c-.177-.159-.493-.656-.462-.725.018-.038.248.1.512.309.264.207.457.405.428.438-.075.088-.371.074-.478-.022z" fill="#9bb92f" stroke-width=".078"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript" xmlns="http://www.w3.org/2000/svg"><path d="M49 51h408v408H49V51zm246.669 314.879l19.463-1.702c.922 7.8 3.067 14.199 6.435 19.198 3.368 4.998 8.597 9.04 15.688 12.124 7.09 3.085 15.067 4.627 23.93 4.627 7.87 0 14.819-1.17 20.845-3.51 6.027-2.34 10.512-5.548 13.455-9.625 2.942-4.077 4.413-8.526 4.413-13.348 0-4.892-1.418-9.164-4.254-12.816-2.836-3.651-7.516-6.718-14.039-9.2-4.183-1.63-13.436-4.165-27.759-7.604s-24.355-6.683-30.099-9.732c-7.445-3.899-12.993-8.739-16.644-14.517-3.652-5.779-5.478-12.249-5.478-19.41 0-7.871 2.234-15.227 6.701-22.069 4.467-6.842 10.99-12.036 19.569-15.581 8.58-3.546 18.116-5.318 28.61-5.318 11.557 0 21.75 1.861 30.577 5.584 8.828 3.722 15.617 9.199 20.368 16.432 4.75 7.232 7.303 15.421 7.657 24.568l-19.782 1.489c-1.064-9.856-4.662-17.301-10.795-22.335-6.133-5.034-15.191-7.551-27.174-7.551-12.479 0-21.573 2.286-27.281 6.86-5.707 4.573-8.561 10.086-8.561 16.538 0 5.602 2.021 10.21 6.062 13.826 3.971 3.617 14.34 7.321 31.109 11.115 16.769 3.793 28.273 7.108 34.513 9.944 9.076 4.183 15.776 9.483 20.101 15.9 4.325 6.417 6.488 13.809 6.488 22.175 0 8.296-2.375 16.113-7.126 23.452-4.751 7.338-11.575 13.046-20.474 17.123-8.898 4.077-18.913 6.116-30.045 6.116-14.11 0-25.933-2.056-35.47-6.169-9.537-4.112-17.017-10.299-22.441-18.559-5.424-8.26-8.278-17.602-8.562-28.025zm-65.728 50.094V278.454h51.583v-18.399H157.938v18.399h51.37v137.519h20.633z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript-def" xmlns="http://www.w3.org/2000/svg"><path d="M457 459H49V51h408v408zM69 71v368h368V71H69z" fill="#0288d1"/><text x="342.219" y="344.544" font-family="ArialMT" font-size="12" fill="#0288d1" transform="translate(-6058.94 -5838) scale(18.1514)"><tspan style="-inkscape-font-specification:sans-serif" font-family="sans-serif" font-weight="400">TS</tspan></text></symbol><symbol viewBox="0 0 24 24" id="url" xmlns="http://www.w3.org/2000/svg"><path d="M16 6h-3v1.9h3a4.1 4.1 0 0 1 4.1 4.1 4.1 4.1 0 0 1-4.1 4.1h-3V18h3a6 6 0 0 0 6-6c0-3.32-2.69-6-6-6M3.9 12A4.1 4.1 0 0 1 8 7.9h3V6H8a6 6 0 0 0-6 6 6 6 0 0 0 6 6h3v-1.9H8c-2.26 0-4.1-1.84-4.1-4.1M8 13h8v-2H8v2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="verilog" xmlns="http://www.w3.org/2000/svg"><path d="M17.282 17.08H6.718V6.513h10.564m4.226 4.226V8.627h-2.113V6.514c0-1.173-.95-2.113-2.113-2.113H15.17V2.288h-2.113v2.113h-2.112V2.288H8.83v2.113H6.718c-1.173 0-2.113.94-2.113 2.113v2.113H2.492v2.113h2.113v2.113H2.492v2.113h2.113v2.113a2.113 2.113 0 0 0 2.113 2.113H8.83v2.113h2.113v-2.113h2.112v2.113h2.113v-2.113h2.113a2.113 2.113 0 0 0 2.113-2.113v-2.113h2.113v-2.113h-2.113V10.74m-6.339 2.113h-2.112V10.74h2.112m2.113-2.113H8.831v6.34h6.338z" fill="#ff7043" stroke-width="1.056"/></symbol><symbol viewBox="0 0 24 23.999999" id="vfl" xmlns="http://www.w3.org/2000/svg"><defs><style>.jra{fill:#f05223}.jrb{fill:url(#jra)}</style><radialGradient id="jra" cx="205.45" cy="208.29" r="225.35" gradientTransform="matrix(.04556 0 0 .0456 2.888 2.88)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffd104" offset="0"/><stop stop-color="#faa60e" offset=".35"/><stop stop-color="#f05023" offset="1"/></radialGradient></defs><title>houdinibadge</title><g stroke-width=".046"><path class="jra" d="M19.97 3H4.03A1.03 1.031 0 0 0 3 4.031v4.135C4.548 6.977 6.563 6.21 8.948 6.21c5.107.003 8.35 3.574 8.348 8.081 0 3.13-1.46 5.485-3.746 6.71h6.42A1.03 1.031 0 0 0 21 19.968V4.031a1.03 1.031 0 0 0-1.03-1.03z" fill="#f4511e"/><path class="jrb" d="M3 17.722v2.247A1.03 1.031 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722z" fill="url(#jra)"/><path class="jra" d="M8.948 8.231c-2.586-.09-4.598.86-5.948 2.264v3.163c.918-2.654 3.447-3.87 5.565-3.85 2.647.027 4.689 2.025 4.7 4.284.012 2.159-.892 3.748-3.33 4.14-1.33.213-3.411-.567-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.905 1.213.293 2.102 1.139 1.921 1.048-.224 1.475-1.156 1.475-1.878 0-.762-.718-1.994-2.498-1.951-2.204.052-3.591 1.639-3.638 3.602-.056 2.468 2.253 4.091 4.622 4.121 3.48.046 5.543-2.24 5.539-5.586-.005-3.029-2.434-5.946-6.085-6.072z" fill="#f05223"/></g></symbol><symbol viewBox="0 0 24 24" id="virtual" xmlns="http://www.w3.org/2000/svg"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h7l-2 3v1h8v-1l-2-3h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 281.25 281.25" id="visualstudio" xmlns="http://www.w3.org/2000/svg"><path d="M196.18 101.74l-52.778 42.444 52.778 40.889V101.74m-136.67 110l-30-18.889v-100L62.843 81.74l47.778 37 96.666-89.222 44.444 27.778v172.22l-55.555 22.222-85.111-81.555-51.555 41.555m3.333-48.889l20.667-19.111-20.667-19.778z" fill="#ab47bc" stroke-width="11.111"/></symbol><symbol viewBox="0 0 300 300" id="vscode" xmlns="http://www.w3.org/2000/svg"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0}.icon-white{fill:#fff}</style></defs><title>BrandVisualStudioCode</title><path d="M218.62 29.953l-105.41 96.92L54.301 82.47 29.955 96.64l58.068 53.359-58.068 53.359 24.346 14.212 58.909-44.402 105.41 96.878 51.424-24.976V54.93zm0 63.744v112.6l-74.719-56.302z" fill="#2196f3" stroke-width="17.15"/></symbol><symbol viewBox="0 0 24 24" id="vue" xmlns="http://www.w3.org/2000/svg"><path d="M1.821 4.15l10.21 17.618L22.24 4.235V4.15h-7.692L12.113 8.33 9.691 4.15H1.82z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179H5.937z" fill="#35495e"/></symbol><symbol viewBox="0 0 420 419" id="watchman" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linecap="round" stroke-linejoin="bevel"><path d="M166.95 145.32a93.935 123.23 0 0 1 92.934 3.263" fill="none" stroke-width="18.467"/><path d="M162.92 137.96L44.63 256.25a174.07 173.93 0 0 0 5.705 16.486l123.68-123.68-11.096-11.096zM266.54 144.04l-11.096 11.096 117.16 117.16a174.07 173.93 0 0 0 5.691-16.5l-111.76-111.76zm170.65 170.65v22.193l17.1 17.1 11.096-11.098-28.195-28.195z" fill="#fff" stroke-width="1.963"/><path d="M167.52 273.36a93.935 123.23 0 0 1 92.934-3.263" fill="none" stroke-width="18.467"/><path d="M49.516 144.56a174.07 173.93 0 0 0-.809 2.213 174.07 173.93 0 0 0-4.757 14.344 174.07 173.93 0 0 0-.016.055l119.56 119.56 11.098-11.096-125.07-125.07zM454.87 64.703l-17.668 17.668v22.191l28.764-28.764-11.096-11.096zm-80.984 80.984l-117.86 117.86 11.098 11.096 112.18-112.18a174.07 173.93 0 0 0-5.416-16.777z" fill="#fff" stroke-width="1.963"/></g><image x="21.229" y="20.262" width="378" height="377.1" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAGjCAYAAABjSWGNAAAgAElEQVR4AeydB3hUVdrH/+fOpNMF JAFUmivFXtZuIBRRQUUTil1RV3et6Lr6rSu6rg3B1dXVtXeBCCioCASIimLDhkFsgAIJSAkhPZmZ 8z3vjReGMJlMueeWmfc88MzMLaf8zp3855zznvcV4MQEkoxA7sVr01PrUrv4pdZNBNA1AHQRkJ0g ZAcJrYOA7Aipv8/Q6JgUKdBkG0iZISDSDVwSaAfAY3z+/bVSAD56L+mfwA5INAqgSkrUCKA2IOQO IcQOIUU5pNwhNZQLia2Q2OyH3OJBxqaiwk4VzfLlj0wg4QmIhG8hNzDJCEgxJH9DjgfevgGBAyCx vxSyh5DoAWA/CHQH0MEFUOoBbJbAeiHEBiED6wMSv0DgF02In1J2Vq+ZP78fXcOJCSQMARakhOnK 5GrICaO3tE3P8B0kERgkgP6Qop8E+gqgL4Bdo5gEphKAwHpIrIHAjxL4DpCrAo1yVfHsHhsSuN3c tAQmwIKUwJ2bGE2TYsjY33qLQOBwCHk4JA6HhgE08kmM9ilpRQUgvwPE1xD4MiC0r2R67dfFz/eq U1IaZ8oETCLAgmQSSM7GHAIj8td38sN7HAROkpDHATgMTWs15hSQvLn4JLBaQHwqEPgQQiwvmpHz XfLi4JY7kQALkhN7JYnqlDd2w4GQnuMk5AkCOBHAQQD4ubTgGZDAFg1ieQCBj6Dhg7SKmhW8LmUB eC6iRQL8xW8RDZ8wn4AUQ8eVHib9YjCELj4nAOhqfjmcY4wE6iGxAkIsCwDvSel5v7iwa1WMefFt TCBqAixIUSPjG6IhkDtmQw/N4xkKTQ4TEkNZgKKhZ/u1DZD4CAKLIMSik/p3WzF5sgjYXiuuQMIS YEFK2K61p2GTJ0tt2Xebj5EyMArAab+vAdlTGS7VVAI0xSeAd6QQb2uBtIW8V8pUvJwZz9XzM2AG AdpoKqpTh3uEdlYA8gwBdDEjX87D0QQaALlMCLzha5Rz2NTc0X3lmsrxCMk1XeWsig6/YFOWr0Ge JoBzICWNhNo6q4ZcGwsJkFOKT4TUZiOgzSqate8aC8vmohKIAAtSAnWm6qaMHPljWl2bzBGapk2A lKMBZKguk/N3JYHPpMCrHuGbuWj6fqWubAFX2hYCLEi2YHdToVIMKSg7SQAXABgDoJObas91tZWA H5DvCeCV2rrUwg/ndqm0tTZcuOMJsCA5vovsqeDwszd19af4LwbEZQAOtKcWXGoCEaiCwAwB7emi Gd0+TqB2cVNMJMCCZCJMt2dFFnIflJQNh8BEgKbkRIrb28T1dySBlULIpzyBwCsLCntud2QNuVK2 EGBBsgW7swqlvUIer+cSQE7UPWI7q3pcm8QlUAtgtibFU4sKu70PCJm4TeWWRUKABSkSSgl5DTkt 3XSqJuVfJDAiRFyfhGw1N8qxBH4AxJP+hrpnit/otcOxteSKKSXAgqQUr/My18216wMXCOB6AH9w Xg25RklOoEoI8bzw+R5ZNKvnj0nOIumaz4KUJF0+YsyWbL/Xdx0gr5BAxyRpNjfTvQQCkPIdDdqD iwqz33NvM7jm0RBgQYqGlguvbfKmrd2MJrPtNBc2gauc5ASElJ8EIO4/eWD2m+xLL7EfBhakBO3f wfmlR2oCtwI4G4CWoM3kZiURASmwGpAPVLTPeXnFk6IxiZqeNE1lQUqwrs4bW3Y0AoF/QIjT2Vdh gnUuN8cgsFYK3LNPoPzFwsKBDcZBfnU/ARYk9/eh3oIh+WXHaELeIZs8bCdIq7gZTCAsgXUQuK9T oPw5FqawnFxzkgXJNV0VuqJD8zcdDCHvlvpG1tDX8FEmkOAE1gohJp/Yv9vLvMbk7p5mQXJp/w0Z u7mPkP7JACbwGpFLO5GrbS4BiRII/H3xzOw3eZOtuWityo0FySrSJpWTO760s8cv7wQEeVVINSlb zoYJJBAB+bGQ4uaiwpxlCdSopGgKC5JLuvm4/PUZmcJ7NSD/DqCDS6rN1WQCdhKY45f+vxYX9vzJ zkpw2ZETYEGKnJVNV1L4h9JxAuI+9jNnUxdwsW4mQFZ4//E31N/NLomc340sSA7uo2H5Gw7zC+3f AjjFwdXkqjEBNxDYKgVuPbl/9rNs+ODc7mJBcmDf5J61toMnNfVfgLgCgNeBVeQqMQF3EpD4XGja NRyTyZndx4LkqH6RYkj+pouEkFMAdHZU1bgyTCBxCEgIPFvvabx52av7lydOs9zfEhYkh/Th0HM2 95Ye//8ADHVIlbgaTCDRCWwWENcWzcyemegNdUv7WJBs7qncXOnVum66XoBMuZFpc3W4eCaQdAQE xFyfz//n4tk9NiRd4x3WYBYkGzuEjBak0J6WwJE2VoOLZgJMANgJIW49qX+3J9jowb7HgQXJBva0 pyhLeO+QkJPYaMGGDuAimUDLBJYJgSuKZuR81/IlfEYVARYkVWRbyHfouLJcBPCUhOzbwiV8mAkw AXsJ1EOKe3Z07HYvh7mwtiNYkCzinXvx2nRvTdq9EriOw0JYBJ2LYQLxEfgCUlywuDB7VXzZ8N2R EmBBipRUHNcNGVt6hJB4CcCAOLLhW5kAE7CeQB1tqF0yI/thdtiqHj4LkkLG+fnSUy7KbpWQ/wBE isKiOGsmwASUEpBLPBouXji9+3qlxSR55ixIih6A3DEbemhe7WV2+6MIMGfLBKwnsA3AxMUzc96w vujkKJEFSUE/D87fOFoT4lkA+yjInrNkAkzAVgLyv/7MhknFz/eqs7UaCVg4C5KJnTpy5I9p9W3b TBGQf2HDBRPBclZMwHkEVnr8KFg4K2e186rm3hqxIJnUd0PGlO4vPHgdAkeZlCVnwwSYgLMJVEHI yxfP6D7d2dV0T+1YkEzoq6H5ZadLIV/gKToTYHIWTMBlBIQQj6bsrLpp/vx+9S6ruuOqy4IUR5fo VnRa2Z1S4jaeoosDJN/KBNxP4DMhcW5RYc6v7m+KfS1gQYqRvR6zKCXtVQiMjDELvo0JMIEEIiCB LR4p8hcVZr+XQM2ytCmapaUlSGF5+WUDvKnpn7EYJUiHcjOYgAkEBNAlIAKLho7deI0J2SVlFjxC irLbh+SXni0EXgTQJspb+XImwASShICAeC6lsuoqXleKrsNZkKLglVdQeiuAf/F6URTQ+FImkLwE lnkatXMWzun2W/IiiK7lLEgR8KL9RQ1tsyia60URXM6XMAEmwAQMAmsDHjFq6WvZJcYBfm2ZAAtS y2z0M7njSzt7/JgD4MRWLuXTTIAJMIFQBCqkkAVLZnRfGOokH9tNgI0adrPY611u/vq+Xr9YzmK0 Fxo+wASYQOQE2gsp3h4ytnRi5Lck55UsSC30+5D8smM8wvMhB9JrARAfZgJMIBoCXiHxVN7YjXcC kmemWiDHYEKAyRu76QzIALkDyQpxmg8xASbABGImQBZ4vt+6XVFcLHwxZ5KgN7IgNevYvPyNl0EI MmDwNDvFH5kAE2AC5hCQmJ9Zh3PnzcupMSfDxMiFp+yC+nFIQdlNEOIpFqMgKPyWCTAB8wkIjKxJ xyLy+GJ+5u7NkUdIet9JkVdQRvuLaJ8RJybABJiAVQS+9kvvqcWFXTdZVaCTy0l6QZo8WWrLVpU9 JoE/ObmjuG5MgAkkJgEB8ZNPw7Di6dnrErOFkbcqqQWJvHVvF2UU2fXCyJHxlUyACTAB0wn86pf+ vOLCnj+ZnrOLMkzaNaTcXOndLja9zGLkoqeVq8oEEpfAfh7hfW/4OaUHJW4TW29ZUo6Q8vNLUreh 42tCYEzriPgKJsAEmIBlBDZDiiGLC7NXWVaigwpKOkEiMSoXnQol5GgH9QNXhQkwASagE6C4SprU 8ooKu61MNiRJNWVH03Q0MmIxSrbHnNvLBNxDgOIqSREoGjq2tL97am1OTZNmhEQGDNtE6asCosAc dJwLE2ACTEAlAVkGIXMXz+jxg8pSnJR3UoyQfreme57FyEmPHteFCTCB8ARENqS2mJw8h78ucc4m gSBJUS7KHgVwfuJ0G7eECTCBJCHQwyM8i3LHbOiRDO1NeEEaMrbsHt70mgyPMreRCSQsgQM8Hu3d kfllXRK2hb83LKEFaUjBxluExN8SvRO5fUyACSQ4AYGBjQjMG5q/vX0itzRhBSlvbOmVAuLeRO48 bhsTYALJQ0AK8UcpamfT1pVEbXVCCtLg/I2jIfEYgKSxIkzUB5TbxQSYQDABMWS76Phsogb5SzhB Gjxu0x81IV7jEBLBDzG/ZwJMIIEInJc3tuyhBGrPrqYklCCReaQIBOYByNzVQn7DBJgAE0g0AhLX Dc0vTbj18YSZ0iILlEaBjyRk0tjsJ9p3jNvDBJhAVAQkpBy/uLD7jKjucvDFCTFCokW+Bsg3WIwc /KRx1ZgAEzCbgIAQz9IyhdkZ25VfAgiSFPoin8DxdkHkcpkAE2ACNhHI1AKBuYmycdb1gjS0oOz/ AJxn08PAxTIBJsAE7CbQ1ePV3kmEPUquFqS8/LJzJXCX3U8Dl88EmAATsJnAwVLUv+R2c3CPzRBj Ln5o/qaDIeRbABJ2k1jMcPhGJsAEkpHAH3oPrNTWlkxb6tbGu9LKLu/sDfsgRfsEQB+3gud6MwEm wAQUEJBC4NyiGTmzFeStPEvXTdlRKAmkaLTxlcVI+ePBBTABJuAyAkJKvKDPILms4lRd1wlSudj0 LwDDXMiaq8wEmAATsIJAG6kFZrnRyMFVgkQ+6iTkX63oUS6DCTABJuBaAhL9Aqhznc871wjS0HM2 99aEeIEdprr2K8IVZwJMwEICQmBMXsGmGy0sMu6iXGHUkHvx2nRPddpHEDg87hZzBkyACTCBpCEg G4UUQ4oKc5a5ocmuGCF5a9IeYjFyw+PEdWQCTMBZBESKFGKGbpnsrIqFrI3jBWno2NIxHII8ZN/x QSbABJhABARkDlI8z7lh06yjBWnImNL9IfF0BMT5EibABJgAE2iRgByVl192bYunHXLCsWtItN9o O8reBztNdcijwtVgAkzA5QTqhSaPK5re/UuntsOxI6RyUXo7i5FTHxuuFxNgAi4kkBaQ4tVRo0od G8DUkYI0JL/sGAlBXrw5MQEmwASYgEkEhMRBtZnifpOyMz0bx03ZDb9gU5a/PvAFgANNby1nyASY ABNgAlIKeeqSGd0XOg2F40ZI/nr/AyxGTntMuD5MgAkkEAEhpHh2RP76Tk5rk6MEacjYjcMBcZXT IHF9mAATYAIJRqC7X3gedVqbHDNld8LoLW3T0xu/BbCf0yBxfZgAE2ACiUhASoxZUpgzxyltc8wI KT29cQqLkVMeC64HE2ACyUBACPnYiRN+6eiUtjpCkIbll50C4AqnQOF6MAEmwASSg4DITvN7pzml rbZP2ZFNfE0GvuGAe055JLgeTIAJJB0BiZGLC3Petbvdto+QajPEP1iM7H4MuHwmwASSmoDA407Y MGurIOWNX3+IRMBV8TqS+qHlxjMBJpCoBA6oyRB32N042wRp8mSpwe99AhApdkPg8pkAE2ACTEDe OHjshkPt5GCbIC0r2XQlII+zs/FcNhNgAkyACewi4NWkeEIfLOw6ZO0bWwRpZH5ZFynkPdY2lUtj AkyACTCB8ATEse9/V3Zp+GvUnbVFkBqaxKiDumZxzkyACTABJhATAYl7cs9aa8vfZ8sFadi40qMA 2KbAMXUQ38QEmAATSBICAuiipabfZUdzLRYkKQIB+R8AFpdrB1oukwkwASbgTgIC8uph+RsOs7r2 lgrDkPxNFwHiWKsbyeUxASbABJhAVAQ8AWgPRXWHCRdb5qnhd48MPwDobkK9OQsmwASYABNQTEAI eVbRjO5vKi5mV/aWjZBq0sVNLEa7uPMbJsAEmIDjCUgpHjjyCmnZXlFLBGnEmC3ZEPJmx9PnCjIB JsAEmEAwgQM7lJddHXxA5XtLBMmX0vhPAG1UNoTzZgJMgAkwAQUEBG63KkSFckEafk7pQZC4SAEm zpIJMAEmwATUE9gn1Z/yV/XFWGB+7feARkdeKxrDZTABJsAEmID5BITENXnjN+9rfs575qh0hPT7 Jthz9iySPzEBJsAEmIDLCGSJQODvquusVJACAZC/OstMy1XD4vyZABNgAslKQEp5xfD8Tb1Utl+Z IA0pKD0ZwDCVlee8mQATYAJMwDICqX7NTwFVlSVlgiQgbQ/2pIwaZ8wEmAATSEYCUpw/ZOzmPqqa rkSQ8saVngSIIaoqzfkyASbABJiALQS8Aj5la0lKBAkBOdkWVFwoE2ACTIAJqCUgxflDz9ncW0Uh pgvS0PzSE3l0pKKrOE8mwASYgCMIeOFRY3FnuiBB4FZHIONKMAEmwASYgBICEvK84eM29jQ7c1MF KW/8+kMkMNLsSnJ+TIAJMAEm4CgCqb6AuMHsGpkqSPB7yL0E7zsyu5c4PybABJiAwwgI4PIR+es7 mVkt0wQpd1zZAQDGmlk5zosJMAEmwAQcS6CNT3j/bGbtTBMkLYAb2WedmV3DeTEBJsAEnE5AXntc /voMs2ppitPT3LPWdhCQl5hVKc6HCdhNwOsVaN9WQ1amQFaGQEaGhox0gdQUAaEBaali19x0Q6PU j1OdG30S/gDQ0CDh9wPVtQFUVUlUVgdQXRNATa20u2lcPhMwk0DnLOE5H8BTZmRqiiBpqekTAcnx jszoEc7DUgIkLL3396JvrxT03T8FPXK8yNnXgy77eJTUo7pG4tdSH9Zv9OGXDT78vK4Rq39uRFV1 QEl5nCkTUE1AAtcD8mlAxP1rK24DhNxc6fV0LfsZwH6qG875M4F4CXTqoOHQAWk4dGAqBv0hFQf0 NOU3WbzV0gXqu58asXJ1A75YWY/NW/xx58kZMAGrCAjg1KKZOQviLS/ub6O3a9mZksUo3n7g+xUR SEkROKR/Kv54eJr+v3t23I+8kpr27O4F/R9+StN0/MZNPnzxTQM++7oeK76pR31D3D8+ldSbM2UC REBKXAUgbkGKe4SUl1+6FAK53C1MwCkEaK3n2CPSMfiEdBx/VLpTqhVXPZZ9WocPP6vDxyvq9fWo uDLjm5mA+QQCwu/pVzRr3zXxZB2XIOXllw2AkCXxVIDvZQJmEKCR0PFHpWHw8Rk48ZjEEKGWuJAw LXq/Fp98WY/GRh45tcSJj1tLQEDcXzQz+2/xlBqfIBVsfBQQptqhx9MYvjf5CPQ5IAUjB2fgrFOz kq/xAN4qqsE7i2vww5rGpGw/N9pRBLZ2kuXdCwsHNsRaq5gFadSo0syaDGwE0CHWwvk+JhALAbKM yzsxA6OGZ6Jfr5RYski4e0iQ5i2qwdIPa1FXz6OmhOtglzRISHleUWH3V2OtbsyCNLRg46US4plY C+b7mEC0BPbt4sGoYZkYdybvMAjHbtbb1Zi7qAYby3zhLuNzTMB8AhIfLC7MoWjhMaWYBElKeeCM udULXp1TeQDtq+DEBFQSGHRQKkYPy8SQE03bEK6yuo7J+73ldZi7sBpfr4p5BsUxbeGKuIOApkE+ PLnTjQMOSv93LDWOWpBIjAB8T4XNW1iDp1/bCRalWNDzPa0ROO7IdH1a7pjD0lq7lM+HIfDNqgZ9 xFT8UW2Yq/gUE4iPAE2fP35fZyOTvwsh/mV8iPQ1FkG6F8AuSwoWpUhR83WREsg9PkMfER0yIDXS W/i6CAjQxtt5C6t1Cz3JExsREONLIiXQe78UPDlllxgZt3mFEFHt8I5KkKSU5E9lr4lpFiWDP7/G Q+CUY2lElIXDBrIQxcMxknsfeqoCbxfVRHIpX8MEwhJoQYzontOEEPPD3tzsZLSCdCqAkAWQKP3v 5Z1s4dMMMH9snQBNyZHFHE3RcbKOwKofGnTLPNrTxIkJxEIgjBhRdjOFEFGFJIpWkF4EcEFLFSdR eviZipZO83EmsAeBA3unYPTwTJw6OHOP4/zBWgLkmohMxskbBCcmECkB8gP59INdWru8vRBiZ2sX GecjFiQpJf3VqDZubOmVRaklMnzcINCxvaabbp9zuns3s1IYCWMdJjNDoLau6XNKCnaFojDa65ZX GimRVd53P/ImW7f0mV31pC0Y40a30Wc2WqnDJUKI51u5ZtfpaASJhl7Td90Z5g2LUhg4SXxKCGD8 WW1w6bi2jqZAISHWl/qwcZMf5OR0yzY/tu8IoKIygIqdgV1CFK4RXg/QsYMHnTtq6NTRg25dPdi/ hxf75XhBZuxOTrSPqfCtamzdHtV6tJObxHUzkUAUYkSlLhJCDI+0+GgE6U0AoyPNmEUpUlLJcR15 Vrj1Guc59aA/urRP59vVDbr7nTW/+pT7hyNh7pHtxYADU3BQ31TdiKNnjvO8kD8/sxKvzK6KSICT 4ynmVkYpRgawbCHEJuNDuNeIBElK2RHA9nAZhTrHohSKSnIdoz+8Z52a6Shfcx9/UY9PvqjDFysb 9BGQE3qka2cPjjg4DUcflgayNnRSuu/RHSj6gA0fnNQndtQlRjGiql4jhHg0kjpHKkgUnvzZSDJs fg2LUnMiyfP5vDFtcMlYZ0zP0R/UpR/V4qtvGxwfW4g8lx91SBpOOCYNp+Y6w+CDnLjSd/nnX3h9 KXm+wbtb2r6thosL2kayZrT7pt3vioUQg3d/bPldpIL0NtmUt5xN+DMsSuH5JNpZp3hYIKuxhe/V 6kHu3BqmgcSJggtS4D4nxHZ66fUqvPh6JU/jJdqXNkx7sjIFJo5vF6sYGTnvK4T4zfjQ0murgiSl bA9gR0sZRHqcRSlSUu69zuMBLhvXDgWj7bOeW7fBh/lLavQpJjJASKTUrq2GYSdl4KqL2tnerH8+ VI73PmYzcds7QnEFTBIjquWVQognW6tuJII0HkDM7sSDK8CiFEwjsd4POzkDt/zZPqMFGg3NXViD L1bWJxbYEK0ho4hDB6TqXi3sXG+a/U41XplTpVsehqgmH3I5ARPFiEgsFEKMaA1JJII0E0B+axlF ep5FKVJS7riOwoVfPLYtzjnNnlHRzHnVmLeoGmWbk9NEOWdfjy5M+WfYw5+e0keeqdB/DLjjieVa RkKA9tZdPiHuabrmRXUQQoT1nNCqIAUCcqYQ5gkS1ZBFqXk/ufOznaOiFwsrMXt+DaqqE2taLtYn gf6AnD0yyzYjkjcX1GD2/GqOwRRrBzrsvusuax/vmlGoFh0phPgi1AnjWKuCNOutquvHnJ71kHGD Wa8sSmaRtD4fWiuiX0/n2vCr/IXCSsycW+14Sznre6WpRK9XYMxpmbjiPHvWmZ54aSdef6tVhy52 4eFyIyCgSIyoZE0IEdbPPHnvDpu2+f5y7uqfGo/PO8nc4Gh/6JOCju09+OTLxJ/zDwvYZSfJ0uvZ aV0w4EBrvQ2QEP3ffeX63iF/cs7ORfSkBAJAyfeN+tpOba3EkYdYG0vqqEPT0KG9B6Wb/dhZyaPX iDrNQRepEqM/3bK14sWnMqesWnVnfILUa9Ckh0s3+Tuv/qkRLEoOenJsqArtKbpuIhldWpdmzK3C P6aU47Ov6sFCFDl38rNX8kMjXn2jGo0+icMHWSdM9GPzrFOzUF0r2S9e5F1m+5WqxOiav28lLyjp dahatGbV1F/DNTTsCGnI2M19hJR3UQbk14tFKRzKxD3X94AUfVOclc5Q58yvxgOPV2Dph3U8PRfH o0UjppXfNWDGvGpoAjjYQj96Rx+ahvbtPPh5nU93PhtHM/hWxQRUidH1d2zDqh9+30wtsGltydQl 4ZoSVpB6DbxxrADOMDIgUfplgw+nHMfTdwaTRH+lX7p33dwR/XqnWNLU5Svq8PgLO/HGuzU85WMi cRpdfvltAxYva9o71L+fNVOuB/VNAVkA/rYtgJ/WsZcHE7vUtKyuubQ9Ro8w3yMIiRH5iAxKaWtL pj4d9Hmvt2GNGobkl84SAmOa30V7H26/gdzbmZvY0MFcnvHmRhswrTTnfvQ5EiJnLoiTFdthA9NA 01H7dfcip5sHbTI1tMnSdMwNjVIfBZBn8A2lPtAG3a9K6rH2170CLMfbLabcT/uYRg/PwinHWec3 j4wdyOiBk3MIkBidaY0YUaP9XunvuqCwZ4t+UVsUpPx86dkuyrYCCLnbUZUokfnof54Na6runN5M 0JrQegMFzjvpj9b8saJ1ohdmVoH+qDsppaUKDD4hA8NPzsAhA2IbUWwvD+CDT+vwzmJn+oEbNSzT 0nXBjz6v04MB0pogJ3sJqBKjG+7YhpV7jox2N1SK/MWF2a/vPrDnuxYFKS9/w3EQ2kd7Xr7nJxal PXkkwieaXrnyAmtMht9bTt4VqvXwD05iRzvUzz29DS44t42p1fr863p9Ayn9UXZS0jToa4QTzja3 veHa+NyMptAW4a7hc+oIqBKj2+7djk/D/dgQeHLxjJwrW2pZi2tIvQbedJEQGNLSjXSc1pNUrCnR vDMthn7KJuHh8Jt+jqboLjjXGu/cT76yU18r2rzFOTbcNCKiP8r33baP7prHbMA53bz6iOsPfVJR XRvAxjJntJ0s8mh9icJy0B6zvr3UrxfSKJy+4zSlSdF3OVlH4M8Xt1MSDqZVMWpqYoe1JVMfaam1 LY6QhuaXLpACEUX645FSS3jdcZz2FNHUDXleUJ30Hf3vVDsmDhG1lzaTjh1tvZcDGimR/z0aOTkp 0ZoC/YK2Kt37nx1YvIzjLVnBm8SIPHqYnSIUI71YKf09lhT23BiqDiFHSLm50ivaVP0XQEQT5zxS CoXWHcdGDc/EnZM6os/+6n8VP/x0BV6aVYXKKmdsmKTRwLgz22DaHftYuk/HeDIoSuzQkzJ0I4kd lQE4ZbT4/c+NmPVONVJThCUboGmt0uMR+KpkD4ssAxO/mkTACWJETdEgVqxZNW1lqGaFFKRex151 tJDy6lA3tHSMRaklMs49ftn4tpg4Qf16EVnO0eZWChXuhETesmmt7N93ddajtNpdpwN6pmBEbia6 dfVie7kfW7fbL9iNjdBHbr+W+nGyBRFsDx63vWsAACAASURBVOmfqntuIevE6hqewjP7mVQlRpOn lmP5iihH+EJsXVsy9a1QbQwpSH37T5oAgWGhbgh3jEUpHB3nnOvYXsOV57eDFRtdyWKSgrrV1tn/ R4aEiPZVPfqvziAXN05LfQ5IwWl5mejU0YMt2wIor7BfmNat9+mjWlpfG/SHiCZMYsZKJvW0zWB9 qR9ULidzCKgSI4qJ9cEnMRnoZK0tmfpYqNaFXEPKKyibC8hRoW6I5BivKUVCyZ5rjjksDffc2kl5 4bQ2MnNuFTY5wGiBhIj+0N9wuXXrImYApvW2eQur9T1NZuQXbx4nHJ2OO28yf/9hqHqRN/cXX68K dYqPRUHgTxeocYIcb4DGVCm6zi/M3tK8KSFHSL0GTnpYADGvfPFIqTlmZ3ymxcxbrwm5rczUCj79 WiWeea0SVQ6YeqHQ3/+7vwsorLrbElmbjh6RpW++JWG321np+tKm0VJ6qoaBikdLhw5MA0XI/ea7 BvicYYzotscHThUjAtkoRPG6kqk/Noe6lyANz9/USwp5W/MLo/3MohQtMbXXTxzfFpeOU2vSvWRZ LR5+Zifo1e405MQMkH8uFRZFVreN3PzQVGN6uoYNZfavsaxYWY9fNvpwyrFqrTIP6puqm+GvXN2I Tb+xKkXz3CkTo3+bE7peCPnz2pJpxc3btJcgHXDwDacC4tzmF8bymUUpFmrm3pOWJnD1Re2Vxy4i bwsPP70Tv2219w8HucK59rL2utFC1857Pd7mwrU4NxqV0BoLWb+tXe9DXb1963L03SaHrRlpAqr9 4tEot6IyALL+49Q6AVViNOVxMs+Pac0oRKVFw9qSqS81P7HXN7b3oJsmAji2+YWxfmZRipVc/PfR lM+F57bF6UPNd5wYXLsHn6jQg+YFH7P6PcVposXb8We1Qbcuez3WVldHaXmDDkpFwag2ujB9v6YR ZBFnRyKHrZ99XY/ynQEce4TaKdE/Hp6u7xejDbycWiYwcUJbFIw23+MGidGCYlNnPjpflP/gA8XF e8ZH2suoIa9g43JAmCZIBjo2dDBIWPOad2KG8vUicoY7Y16VrdMpZC1HfvdIkJI1vTK7Cq+9UWXr iClnXw/yz2ijIuz1Ht1Khh5Pv7rTEVabe1TMAR9IjGhfndlJgRjpVfT40X/hrJzVwfXd46ckOVSt FVX/BmD6LkkeKQVjV/ueHkrVgfTojyB5bq6qtmfaiJydXj6haR8VbTBN5kR7eMjlEfXEdz82gmIg WZ0qq6Ue/ZnqQF7RVSUa9Xfr6sGOioDt08Oq2hhLvm4TI72NQi5vvkF2D0HKOfjKgwBcHwuQSO5h UYqEUnzXkPHChflqjRfuf2yHvpM/vprGdnf/vim4dFw7XH1RO9CGUjsTTR9R8Luff/FhQ5kfASn1 zZ121YmE4PwxbdDogx5M0w5h+mZVA35c68OQE9QZPPTaLwWnDs7E9h0BikRqF27HlKtKjMizyjtL TJ2m24OZEFi7pmTaouCDe0zZ5Y0tPQ8SLwdfoOL9iNwM3HyV+ebHyR66QtUGOOMZoCm619+2xw9d v14p+nTQaUPUrocZbW3p1fDYTYEEySlp89Q2S9MNSM4bY/7USfOyWvtMDmxnvV1tS+j3lBShj2DH nBbz7pHWmqeff3lWFZ6fWRnRtYl4kSoxeuSZCt3PokpmUorFSwqzhwaXsccIqffASRcBOD74AhXv KaTx5q1+0EY7M1Oyegnvso8Hl09ohzNPVfflnzm3Gv99YaflfugO6OnVR3yTrmwPEiW7Erk9euqV nfr+KtqP01KimE7kk418wdEIhabT7EpHHpKGC85pq6+30FSelYnaTgYPZAlI9VCVaOqW9iuFDXmg qnCb8724oK0+VWt2NawQI6qzEGi/tmTqA8H133OEVFBKw6c9FCv4YrPf80gpfqL0B2/a5H3izyhM DnZEcqV1IfJArvoXdphm66dKvm/QfynG6o2a3DSRqfa4s+wfMVE/vrmgOuTIrjUO8Zy3IuAjjd4L 36pC6WZ7tx3Ewymae0mMzj/H/GfKKjEy2iok9i8qzPl112fjDb3mFWwsBUR28DHV71WJ0pz51Xjs +cQOl2yFJd3N/9ymx8pR/RwY+dOUF5luF4xWN9ozymrt9YH/7sDC98yZQ9+3iwdnjshCwSj72/XQ UxV4u6imteabep6CAF51oZrQB8EVjSYMQvB9bnqfKGKkM5cYubgw512D/64puxMn/NLRG/DcbZyw 6lXV9B1t1mvbRkOihkomx6g3XKHONxv5orvlX9vx68aWp6fMfkbox8kjd3dW7pamtXrTH2zyTk7P plmJPFiv+KYeRR/U6lN5FIPKrkRulAb8IVWfygs3/Whm/Wi9jb6LZHBxxMHqpvDyTsrAth0B/Jig xg6qxOh/L+3EnPnW/kjRny8hvlpbMnW58aztEqQDD/rrURC41Dhh5SuLUnS06aGk0BGqEnldePyF naD1EKsSmS3/5RJ1AhtJO2hK6//u367UcovMo8kwYulHTUYRqr0ctNTunH29GHx8Bsj4wMrNpt+u brJKpLJVJRJcenLJ4i+REhnKXKTAgpbEqPCtaptQiQ1rS6bONQrfJUi9Dp40EsAZxgmrX1mUIiNO bkFUrkfQw0lB9KxMNNq74jz1cZlaahO1+dZ7t+um0i1dY/ZxcpRKI4YPP6unxV0c2Nseg42DD0rV nbeSAYJViUZlbxXVIC1NA4WcUJHIBD4zQ8Pn31jXLhXtMPIkMbpkrPk/Qu0VI2qdrF9bMu0Zo527 BKn3oEkXmOkyyCggmlcWpfC0/nJJO6WL/HdOK8f8peasmYRvye6zfQ9IsSykwe5Sm96RR3ISom+/ t9YCLbgeFPPo4y/qdXGiUOoUE8nqRKO01FSBL1ZaN6Kg+FiffFkP8rWoKs4STYt2bO9ByQ+Nlo72 ze6/xBUjIiXarS2Zep/BbJcg9Rk46RoA/YwTdr2yKO1Nnhb6aUGYFsVVJLJQuu/RHVi52ro/SEY7 aPqxn8Wjg+dmkBCV61M6ofYSGXWz8pWixH74WR2+WtUACoZn9aZf8o9nbFy3st0kghSm5OjD1Kwr 0QisXRtND3hIG2ndllSJ0bPTKzFjrl3TdHv0QlrvQ/76xNpvH9Qrs0uQeg2c9A8Anfe41KYPLEq7 we/X3YsJZ6nzETb7nWrQ2okdsXbItFvFBund9PZ890JhJf7vvnJ9zcQOLwZ71ib0p81b/Hj/kzpQ yIWMdIH9e1jnFql9O49pVoWhWxf6KO2RIiexZDWqItEPnjOGZeplbCxzj1m4KjGiH2SvzrF2Wj5c v2oy8Maakmnr6RpdkJp82FVOBcQugQqXgRXnWJSAwwel4rF7OiubZ6fQ4k+/at8ud/rCDein3tqM /O7d/sB2fP51gy1eC2L5vlD8n/eW12HVj43IzBCwwl8f+Yhb9UOjLXt5SCjeXlyL9FSh7Hknwftt WwA/rbNvijbSZyH/jCxMnGD+uiqJEX0fHJWE9t6akqlfU510Aeox8KoDpMCNjqokoJvdqvDo4AaT 8NzjM3D3X9WFGievC9PftPfBJFdH7dtqyh676W9UYfK0cny8ot62EA3xNo42epJF3k/rfPo2hpxu akdM6ekCxcvNinkTXeuNdSUKRKgqIi15hSfvEbSu5NREYnTlBUkiRmTWAJSsLZm6lPpDf7r98PcF 9nDa4Ji+MmJwmD21Y0QSdeLmWfJQoNJb9z8p6qNNf3SMB8vrgbJf/TPnVYOmIrdud8/0jMGlpdeP Pq8D/acfKqOHZYJc5qhIJx6Trlv92bm29uTLO1G+w6/kjzIxu+L8droFnhN94CWbGFF/aEAf41nW BUlqYn9h3ZYTo+yIX5NJlFQ9kAbsa2/fhlU/WG+8YJRvvHbsYP7sMDkSJQ8dm7YkjhAZvIzX4o9q Qf8piuqo4Vkg7+dmp306emwXc9oXQ/14x40dzW6enh+53UlPE3oIFSUFxJCpqu8+OaB13DRdEB8p sb/xUf+r0HvQjWcC4hTjoBNfk2FNidZUVMwbU3+SJR25VdlQZp73gXieE/pjkD/KHF9cNBp64L8V IH9zZLGVDIlCXsxfUqOviZgdnHDRB7Uod4BFGnkJ+fDzemiK9mmRWTgZcnz6pf17lc4ckYmrLzZ/ Y7grvKELYG3J1Ifoe6uPkITUekp9b7Ozv8qJPFJS5RKEetSJfv0aTJjCp82VJLQ//2JCZs5+9Fus 3btLa7CguAan5WXihsvN/4PWYsEWnfh5XaPuZd7nB+iPttmJ8iTBe+H1Sj3on9n5R5IfRTy+5lLz +84VYtQEKCc3V3qLi4VPHyEdMOCma4RA70jg2X1NIo6UKKYJuc5RkWio/uQr9lnStdQm2ogZT7hl cm1EFkO0sZQTdN9t5GFjZ6XEMYfHt6eHhN4JIySjX/1+6KMYVcYOtFcpI03Tpwgrdlr7PJEYXXtZ UosRdbOW0rby6Z+/nbZTN3ESQvY0Ot8NrzRSomiGZicydCDLLyuT7groTDVi9NQrlfofbSvbw2XZ R4AMEd54txpTHt9hXyUUlkzGDuRdQ0UaNTwTz0ztAnKlZFVSJUZkPetEg41wXGUA+9F5w+a2R7iL nXhu3qIaUOwOs5OVokTid+4Zarwv/PupCpCTVE5MIJEIvPZGFR58wvzvvcHooTv3gdlrckbewa8q xcjOvYXBbYzmfUBqetgjbdSoUpqYNX9yNpraxHgthUhwqyjRnLFheh5j81u87Z8PlevOK1u8gE8w ARcToHUzitOlKt11c0cMO1mN1wiqM4VZUTFNRyMjN4oRMZEIdKNXrTrTY2lAPrMfIpWiRNNpZidy B3PdZe2VLNBSXW+6axve+9iejY1ms+L8mEBLBChkxgXX/qYbtbR0TTzHb/lzB9AoxuykKiCpm8WI GAsNXejVK4Xs7OQ9SJE8ECRKlMz+1WFMpz3xkjmRZ7MyBSaObwearzY7kbXZ7Hersd7CgHpmt4Hz YwLRECjb7Mdjz1cgINVY4NHfE/JcMdMkJ6QsRmF6V4ocOuvVAgFXj5CMJjpdlLp29mD8mWqcpJIY Pf3aTlBUUk5MIF4CNbXWWprFU18yB//PsxXw+SQorpbZieJ0kfd18vsYT1IlRq+/Ve3aabpmPHXH 3l4BdEmUP2NOFaVuXTwYO1qNGJFVFXnr5sQEzCIg3aNHu5r8+Is7dR91tLnc7ERRWj2aiNlyjdw9 me36jNpIYmTW7I3ZzKLPT+xL93ghRQe4fc4uqPVOE6Xe+6XgySlqonok1gMZ1In8lgnEQID2pdXU Slx+nvmRVcnVkNeLqEcjpxybjr9f1yGG1oS/JfG++1KHpEnR9CZ88911lkRJhfUdrSlFY+jQr5c6 MaJFzMT5deSu54tr61wCtNVh2pNqzMJpI3c0338So9tvMN8XX+KJkf486aA0IPEEiZpntyhRBM7H 71MzMiKXIG4173TunzKuWaIQeGdxDf4xpVxJcyL9UapKjMgNWIL+EG0aIQkI8yVcyaMQfaZ2iRIF 1vv3nftEX+EI7qBpCbftwo6gWXwJEzCVAIXquO4favYqkSiF8+hysqKRkRN9UprYaem5F69NJ08N 5jtSMrGW8WZltSgdc1gaptyuRoz+99JOR7uRj7ev+H4mYCaBku8bcOmNW5TsVaJN7aEcotL3/x8K pukSXIyaur06q4NXAubv/jTzqTIhLxIlSqr3KdHDeM+taqK8PvxMhZIvlgl4OQsm4FgCFMLi+cIm /3dm7/8zPIXTd5OSqu9/UogRgFQEMryQSHdosFhTH3LVovTFynplYnT/Yzuw6P1aU3lwZkwgWQiQ B+9HnlWzgdYQueUr6pR8/5NFjOhZDABtvBCIz1e9i55qlaJkeHUwG8fkqeVY9im7AjKbK+eXXATI EzptoA0EpOk+JEmUDGEyk+qbC2rw2PPJs8fQ70EmBehL6DWk5g+IKlFqXo4ZnynC66df2R/N0oy2 cB5MwAkE6A88xVdS9QPSrDaSGJGAJlMSgUAaCVLSjJCMznWDKN04eRu++a7BqDK/MgEmYBIBMpuu b5BQ4dXBjComoxgRN02KtmRll24GRLflQaLk1OHw9XewGLnteeL6uosAbZ+g/XxOS8kqRkY/0Agp aRMtGHo9wJUKwkzECvWKm7diza+Nsd7O9zEBJhAhAdrP1+iTuGSs+a6GIqzCHpeRk+Rkm6YLBiAR aJPUgkQwCt+q1pnYLUr0ML65sBrr1vuC+4jfMwEmoJDAK7Or0NAgbf9RSt9/w3xcYXMdnbUAPCRI zvh5YCMqu0WJHsbpc6uweYvfRgpcNBNITgL0/acwFuG8L6gkw2K0my4JEq0jJX2yS5ToYXx5dhW2 lbMYJf1DyABsI0DT936/NH3zfGsNYjHak1DST9kF47BalOhh5MB6wT3A75mAfQTI0Ims71TELgrV Khajvanw6KgZExIl8hmnOrEYqSbM+TOB6AksKK7FPx9S4yk8uDb0/f/fy+r/zgSX6Yb3JEgujA+p Fq1qUWIxUtt/nDsTiIfAex/XKRUl4/tfV58osbrjob3nvSRITZ4H9zye1J+yMgVy9lU3m0luRkYO yUxqxtx4JuBkAocMUOcvgL7/+WeYH2rdyTwjrZu6v7qR1sBh15EYTRzfTolvquCmUuRJEWR2Hnwu Gd5npFPrOTEB5xG47rL2yr//FBKdEsc2293/EvCzIO3mAavEyCiS9j4JAcyc17QXyjieDK8eXr1M hm52VRszMwQun6D+x6gBhUXJINH0KqBV0Z8FdiUNWC5GRldccX47x/rUMurIr0wg0Ql0bK9ZKkYG TxKliwuSfiuogQM0Qkp6d9JWj4x20f/9DbkuSfEKHr43BxPmcy0vCIehw6eiIdCtiwdjR7dRPk3X Up14pNREJiBkJQlScvk4b/ZU0DDdijWjZsXu9ZEeSq8XePpVtjHZC06IA7SJkRMTiJdA7/1S8OSU zvFmE/f99P0nv3rkyihZk9S0eg0yuUdIVs4Zt/agjTuzDSZO4OF7a5z4PBMwg0D/fs4QI6MtNFPi 1JAYRh1Vvnr8qNEgkncNyQprmmg7kESJLPA4MQG7CGRkJL4F5CEDUvGfu+0fGTXv42QWJQ2o0gSQ lNuFnShGxsNJ0SztcvRo1IFf3UugPs64jhr9VUjgdPxR6Zh2xz6ObWGyilIDtNqkXENyshgZ35Kz R2aB/jAkc3wUgwW/RkegsZHX11oiNuzkDNzy5w4tnXbMcRIlSkm1ppRVvUOTkOodNzmmmwFVYvT6 73GVzGzqmSMy9fqmpyX2L1YzmXFeTKAlAqOHZyoTI4pAa3YiUco/I8vsbJ2aX13x873qvIDY4dQa ml0vVWJ0273b8elXTdbzNN1mZiI3I5QK36pC6WYOUWEmW84reQgUjMoC7flTkcgZK/m/o2SMbMwq xwgcakQiMCtfB+aj65AmZHIIkhVi9MRLO6FipESi9OIjXXFQ3xQHPkdcJSbgbAIXntvGEjGi6bWX Z5lvtk2ilAQjJX2mToOQCT9CskKMjK8kiZIqV0CP/qszjj5MndNHow38ygQShQBto7gwX81Witsf 2D0yMniRbzoWJYNGNK9NAyNNAluiuc1t11opRgabJ1/eielvmv9LifK/99ZOGHJChlEUvzIBJtAC Ado+QdsoVKS/3bMdy1eE9rqmUpTMXhJQwSa2POVmuk8LaFpZbBk4/y47xMigQh4XVPxSovxvu7YD Rg3j8BUGa35lAs0JXHNpe6j64339Hdvw+dfhPa6pEiUSWTLOSMC0ldrkFVLobxKtgXaKkcGSHkpy B2L2Qiflf93E9qANjDPnJp+ncIMvvzKB5gQ6ddBwwTltlfmlu/KWrfh5XWPzYkN+pu8/JcNXXciL Yjh47WXt9bso5HrCJCFLqS3erBp/WU2CzQA5QYyMB4UWOn0+4PLzzJ/HvuK8dsjK0KDC5NSoP78y AbcQ+EOfFDx2jxrvCxTldfa71Vi/0RcVDhIl8lFp9tRhoomSDDQtHWnz5uWQzCaM1DpJjIwnd8bc KvzvJTUOMcj31VUXqjFnNerPr0zA6QTI+4JKMZo+typqMTKY0fS9ijVlEqVEmb4T0DYRLyNM2gYD nptfad7Y2LdjZjuC9xnFmi/tI3jkGTWO1c85PQvU9pQU3kAba//wfe4lcFpeJu66uaOSBtDI6OnX dmLzlvj2AKoUpTOGun9NSRMB3ZZBjxgrpVgvhDxQSY9alCn9QSbPBmanux4q37XpNd68ac63vkHi 5qvMd11CbScXZLPnV2N9aXTTCvG2i+9nAnYRoDhGKqbDqT1vLqjBo89VQJrkickILWP29N31l7fX 16oXFNfa1Q1xlys0/EqZ6CMkoQXWx52jjRmoEiPagf3+7zuwzWoePTQkcioSjQ6fe6gLDh/Ee5VU 8OU8nUWAjIVUidGc+dW6H0mzxMggp2qkRD9yR+S61hjA37ApRx8hNU3ZSbhWkFSKkeEOxHiYzHol kbv5n9vNym6vfKbc3glDT3Ltw7lXe/gAE2hO4OqL2imLHUTeVh57Xs2aL7WDREmFRxcXi1JpcbHQ p3V0QZJC6MOl5p3u9M9uFCOD6Zff1uNPt2zFO4vV2JP87S8dcM5p5vrVM+ruhFdaxD7qEB4JOqEv rKxD504e3eHwGEXP9qtzqkDeVlQnVW7GXClKQQMifQ0JkL8C7loQd7MYGQ/7T+sa8eLrVfD7ocQY 46qL2qFDew3PvGa+J2KjDXa9nnB0Ouh/8Ue1mLuoBt+sijMIkF0NMbFcjwf6jxBVTkRNrGpMWR06 IBVTFcYxou0TVoZ7MITP7A28JEqNPmDJMpesKQUNiHRB8gY8P/pFIKaHxI6bEkGMDG5bt/vx3xeb fpGpsBAcf1YbZKQLPPqc+l99RpusfM09PgP0f+F7tZi3sBrf/RTZpkUr66i6LCEAGjEksvk/rY/Q H1pVibZl2OFRW5Uo3XZNB/h9cpcXclXczMlXrjHy8dCbIwZOqawVlbcCQv9snHTiayKJkcE3EAA+ +bIebdto6N8v1Ths2utBfVPRsb0Hm7b4UbHTGT88AhIgsTQr9TkgBWT+26mjB1u2BVBe4Yx2mtW+ UPmQEJ11ahZ0p7uHmjd9+eaCauxwyHNC7R53Vhtcc0mTd4JQHOI99u+nKvDGu2qmziOp2+ff1CMz Q8OAA8397p9yXAZ+2eDT/0dSD7uuEZDPrymZ9iWVrwvQqlV3yt4DbzofgHPj+gL6XhsVpt3B8Uzs 6hQq97Ov1DyYlDftYj9zRBbW/OrDr1HuNlfBhKYUzj2jjel7pw7snaL7+WvXVmsS4MrEFKbTh2bi v/d2xjGHmydERj/TNHJdvUm2zkamMb6SN5ILzjXvh0vzakyeWo6iD+yf2iJRUvGD1BWipImpa7+d qtsx7BoR9Rk4aSSAfs07zCmfE3FkFIotPZgej8Ah/c39tWSURdNbldUSqx0wtdW/Xwp65vy+jGlU 0KRXGhWSAGdladj0mx87qxJDmIafkoHrr1DnXLeqOoDnZ6rxVB9N12ZmCPzpwvagTd+q0rW3b8OK b8I7SVVVdqh86QdpMoqSP+C9bd2qKfpDt0uQeg2adAyAY0OBsvtYsoiRwfmrkgb9F+qRiqzIjjks TRc9KsfOJCFw8rHpSqswoF+qPq2Vnq5hQ5kP1TXO+OUfbaPph8S1l7bX14q6dNr1tY02m1avX/5F vel771ottNkFBx+UiosK2mLkYPM3ulNR5H2BRkY0neW0pFKUflzr078DDmvzzqWF3f5u1GnXk917 0KT9AZxhnHDKa7KJkcG95IdG/LY1gOOPVvMHm0ZgNK21YmW9aTvRjbpH+kpThxeca77T2VDlD/xD qm6BlpoisHa9zzFTUqHqGnzsxGPS8eeLm+L67Ntl19c1+BJT39Pifumm+NzkxFMhcoMzeVJH9NpP TXRk8r7wyLMVqKl17g8TVaJEcdRoZmSjjf0b4tn4Zm3J1KeM47ue8D4DJmVA4FLjhBNek1WMDPZk Fk4PUJ6iTa40rUW/trduD2BbufVTWrQLnkYsVkbBHXRQKgpGNa1d/bCmEY0ONcr74xFpuPrC9nro gpx91UxrGs+Z8frdj436pk3js9Wv5HlBpck6bUb97wvusDZVJUr0t8RhorR4bcnUN4xnbZcg9Rt4 fVVAaLcYJ+x+/csl7fSpFrPr4RQDhkjbRb9mPvi0DgJCN0yI9L5Ir+vbKwW0QL55qx8/r7N+CoP+ CLZv58FBfdX8Im6JA00LkZUfrddRHWgvmBPSUYem4U8XtsPFBW3RI9saITLa/ez0Sqz5xfpngJwC k+cF+qGgKlGwzKddth+PREnFd8NJoiQhX1tbMu1Do993CdLPqx6q7T3wxisBYc0cilGDEK80RXH2 SPMXM+9/bAeWfBg67HCIajjm0I6KAFaubkCbTE2JKFFDaZMppa9t2GBK0Tc7tPMoa1u4jqSpSwrh QRM4q35oBJng25EOGZCKyye0xcQJ7ZQZeoRr18x51SicZ32wx4EHpuLisW11k/1w9Yvn3JMv78Qr c+w31IilDZ9+mdiipEnt32tWTf3RYLNLkOhArwE3jxQCvY2TdryqEqMpj+/QN0/a0SYzyiQzaZV7 laiOhw5M09eVSJSsHDHQ1N2n9GuwrfUjJaNvDhuYpk+PEWea0rBKmPr3TcGl49rpI4QDelo7SjTa To5En/h9c7ZxzIrXkUOawkb03l9du+/9zw68VWTfHiMzOKoUpa9WNcQdWiOeNgrg1jWrpu6Ky7OH IPUeNOkQAMfHU0A896oUIze7Zg9mSsN4r1fgYEVm4bSuRCOGb75r0PfxBJet+j198eoaJFRZF0ZS /yMObhKm2jqJ739uVGbw0Wf/FN2SjEIH0KZeuxK5ynnyFetdS106ri2uPF9tYMnrbt+m/4izi62Z 5aoSpRG5mfji2wb8ttWWOettiwtzCjVkfQAAIABJREFU/i+Y056CNHBSVwBjgi+w6j2LUeSkv/y2 AVWKjQGGn5Jpy36lku8b8frb1boQqBLdSEjTWs4F57RFTZ3U15giuSeSa2jf1QXntMHNV3cAbeK1 Ky1fUYfHX9iJtxU5922pXZ06aLj8vHYw239bcHlk1v23e7Y70cQ5uJpRv1clSqcOtkeUpBTL166a +mIwiD0Eqc+gSWRz9OfgC6x4z2IUPWVaiCevC7Q/RVWi/UoZGZrlmwdp2oxEd96iGgT8AFnG2ZVI mC48ty12VjWNmGKtR86+Hpx/Tlvcek0HJe6hIq0X+fp7bnqlbk1ntfkvmbBTmHHyGqIqGaEjGhqc a9YdT9tJlMgNmNkM7RAlTcjZa0qmLQrmsYcgHTngwfJaUXUTAHVPTHDppH6KDBhozShRpumaIdv1 kfbxqLTAo4Jo0blrZ3tMw8l9DU0nLHivVl/Tor1EdiVy0UPCtG1HAD+uidxWvMs+TUL09+s7mu6r LFoW0/5XgUeeqcDPNljSnX9OG9D0pMr0YmElnplu/fSjyjaFypvWkhNBlITEY2tWTVsZ3Ma9Yk7k FWxcDghLPDawGAV3RezvyckmsSRHmyoT/TGjMOx2pe7ZXowelqnUnUwkbaNRBnkWX/R+bYtrTB3b a/pGXHIManci56E0NWd29NNI2kWbry/KbwsVPiiDy3/46Qp9RB18LNHfX3dZeyVha668ZSt+Xhf5 j65YOQuBAUUzcr4Lvn9vQRpb+m9IXBd8kYr3LEbmU504vq3uGdn8nHfnSFMihsv83Uetfdeze5Mw qdgaEG1LSJRopEpGEB4N6NhBQ98DUkBTfXYnCjlCnrvtECJqO7mF+scNHZVj+L/7tieM8UK0sFSJ 0hU3b8WaX5WKUsVJA7I7TZ68Z9yjEIK0cRykeC1aMNFcz2IUDa3orqXF4j9doNZ6iUK70wjBbl94 ZKlGMaTI3Qyn3QTI/Y9hGLL7qLXvaGMvTdOpTGS88PLsKmwrt8VCTGXTosrbpaK0aPHMnOHNG7rH GhKd7HPQLTXQpLIREotR8y4w9zNt7ly3wQdyO68qHdDDC7LCa2gEvv3ePgetFPPo4y/q9bAdZApv p/m0KtbR5EuRgW+9dzu+/V7pL9uwVdqvu1f3MpE/Su30MW3kJTdANDJN9kTfQRWb5unH3rJP69XE FhPylbUl04qb991eIyS6YEhB6W8C6NL84ng/sxjFSzDy+7t38+Lc07OUzDEH14KcVU5/swpbttn/ K5W8HdAak0rLw+C2O+X9C4WVeO2Navh89v5xpj9g9GtddbIruqvqdsWTf1amwMTx7ZR835VM3wlt 1OIZ3d5q3ua9Rkh0Qe+BN51EMd2aXxzPZxajeOhFf29lVUCfV8/KND8SZXBtyAcdCd9v2wIgZ7B2 ps1b/Hj/kzqQp3QK206/1hM5kX82GhF9sbLBMs8SoXimpQndKSo5R1Wd/jGlPOGtZ2NhSE6CVY6U Fr5Xq+99jKVuoe5Jlbj+p1VT97KQakGQbuwJiGGhMorlGDlOHHOa+UP4ZDDtjoV38D3kJ67BB5AH ApXp+KPS9TDMX5XUg8KT25nKNvtRvLwOZA1Hgd5UBQG0q43kXeH2B7brU5VWungK1V4ajf7v/s6g uFMqE60X3fVQue7WSWU5bs5bpSileIW+FGBSPLHvFhbmTAnFOqQg9Rn0Vz8gJ4a6IdpjtMCuIuoj mbLOX2J/6OFoedhx/berG/S9J4MVbqKldg04MFXf/PnLRp8jgp9RXJ+lH9Xhp3U+PRJnTjd3j5im v1GFO6eVY/mKekeEzZg4oa3ug0/1M134VjUee36na4MrquYTnD+JEq0jZ2WY64iZNuKmmidKs9eW TN1ruo7aEVKQ9usyZZOWVTUJQFw/e0iMVLgIof0wtIufU+QE1pf68M6SWqSlqgljEVyTU47NAE3j kLcFJyRqe9EHtfi11I8O7TRYEejOzHbTAv49/9mBDz6pc0RgwWOPSMNVF7XHqbnqrRtpi8FLr7vT U7eZz0A0eTU0Sn00QwJipkcHs0RJSPlQ8w2xRvtCCtK6dXcG+gyYdAoE+hgXRvuqUozs3JwZLQcn XU9RMmmXN8WfoXhAKtOgP6Tqng0oTLRTQkWvW+/TvT5s2uJHp44ePTihSgbx5j3r7Wrc/98KFH9U 65jRAe11u3Zie0tiNd3yr+1Y/AHPgsTyHNHUGlnbOlGU/H55/brvpoWMlBhSkAjAAQMn7S8EhsQC g8UoFmrW3UMjF4oSe9xRasKjB7eEzM8zMzR8/Z29C+/BdSLXOfOX1FjGILjsSN6/8W41pjxeoY/q yDjFCYnWCGktmLxDq07U/kl3bQeNbDnFTkC1KJERRdQRlwXWLH29+90ttapFQeo98AYfhLispRtb Os5i1BIZZx3/cW2jbpGmKhJtcGv1taUxbWyLShtcl+D3xOClWbSxMoDjjlQvzsFlh3pPcXumPlGh W5FV7HSGENH+LgoaSBGcaSuB6vTcjErQfiq7jTVUt9Oq/FWKEu19ilqUBApbWj8iJi0K0lEDp5bV iqprAUT8TWUxsuoxM6ecHTubTMPT0zRY4biUotKSb7MNZT49tIU5rYg/F3KWSsK0s1KCnKhancik lox0SJBos69T0ojcDDx+b2dLng1qM5l0v2NxOAynsFZZDxKlX0t9GGNyFG5aU4pWlATEA2tKppa0 1N6QG2ONi/MKSmcDONv4HO6VxSgcHeefOy0vEzdeoX5To0Hi6dcqQVZjTkvkqJb2VV2p2P0StZvW huYuqsE3NoSND8edfAWeOTxTubNeow40RUcjI/rDyUkdgd77peDJKZ1NL4BM8p9+LSIryIBX+rss KOy5vaVKtDhCohv6DLipEwROb+lm4ziLkUHCva80fbXkwzp4NHMtc1oiQvuiaCqPDC2ctlZAZrOv zKlCQ4Oa/VsffV6nmzFPf7Pa1vDRofrmgnPb4I4bO4IiB1uR6IfJs9Mro1+LsKJyCVYGjb7JFRB5 1DAzGSOllasbQLHMwqTPFxX2eCTMeYQdIeXmr+/rEZ4fw2XAYhSOjjvP0Y57CmNuVZozv1p3Bkqe FpyWPB6A9m+dPjQzbstEmpKjX5M//2KvR4tQjIeckKH/oVJtfWmU7RQHvUZ9kulV5Ujp4WcqwqG8 Z/HMPUOWN784rCDRxXkFpd8DOLD5jfSZxSgUlcQ4RtE9J09SHzogmBYFWCPvzQHnLKMEVw8d2ms4 8uA0HH5wKg7okYIe2R60ydL2uMb4QKbltFb2/U+NesTdVT80wOc8vdVDZYwengmasrUq0b6qJ18O afVrVRWSvhw7RElInFRUmLMsHPxIBOlBALRJdo/EYrQHjoT8QPuVaDf+OQrcPoUDZncgwHB1a36O XBNlpGtITxfw+yRq6qTugbqx0dnrISSk487MwrgzrRsJE7up/yMPK7ypvflzZMfnfr1S8Ph9ataU QoyUtneS2V0LC0XYn2Vh15AIUu+BN9H8wkXBwFiMgmkk7nsaqZAvvPKdARx7RMTGlnED+eMR6Ti4 f6rulYCC3zk50Zw5hUCg/UJVNRL1DdKxIzziqGnAhLPb4P7/64RBijdHB/cbGS7c8eAOfPOdM7x3 BNctWd9v3xHAF9824NTB5o6OaU2JQqzTJvygNOutwnazgj6HfNuqIHU64cGN6XW7zb9ZjEJyTOiD P/zciIXv18LjESDv3lak7K5e5B6Xgb69UnQT8dLNYX9YWVElV5dB1oMU4v4/d3fG4YOsNW0n9z/P z6zi2EUOfIJ+2+q3RJSkEHevLZm6qjUErU7ZUQZDC0pnSiCfpm9UDPEp0Nbsd6pbqyufdwABKyLS hmqmU02kQ9XVScdIiGh96IbLrTPpN9pPBhxvLqwGuWzi5GwCNFr+9537mF5Jipf2n2d3NAqZ0aWo sFNYiwcqPCJByhu7cdzE8e1eUyFGHGzL9GdAeYb79/DirBHqg/+FasiyT+t0x7orvtljOiDUpUl9 jIRoxCmZuOkq64WIwD/1SiVmzHXePrOkfihaabwqUVr6Ue36ISdk7tdK8frpiASp9LfGU7O7eOdH kmE017AYRUPLeddSWJGrLmxnS8U+/6ZeN6H+8LM6W8p3aqFkpj5qWJbu6seOOvKoyA7q5pV5+KBU TLnd/JESgH5CiJ9aq2lEgiSlfA3AuNYyi+Y8i1E0tJx7bbcuHj3ECK1P2JHW/NqoC9O7xbVwumWb Sj5tszR9H9Gl49RHbW2pHbRW9PpbPPXeEh+3HD/msDTcc2sns6vbTQixubVMIxWkCwG80FpmkZ5n MYqUlHuus9r1UCgy09+swluLakB7gJIl0X4S2nk/api5llLR8CMLOgqi58SNzdG0g6/dTcBkUSoW QgzenXvL7yIVpA4AylvOJvIzLEaRs3LblakpAhcXtEXBaHtGSwavT76ox4L3an6PrOrs/UBGnaN5 peCHucel47QhmZY5Pm2pfryvqCUy7j9uoihdI4R4NBIiEQkSZSSlfBPA6EgybekaFqOWyCTW8aMO SdN/tZN3b7sTWfkUfVCD1T81QrpYm8hI4ZD+qcg7McNSrwot9R+NRskHnVO9arRUbz4eHQGTRClb CLEpkpKjEaSxAKZHkmmoa1iMQlFJ7GMFo7Jwxfn2GD2EIkvrGxQGfNWPDa4QJxKhAf1SccIx6SCW TkhLljV5KP92NW9wdUJ/WFEH+mF5500xuxFbJIQYHmk9oxEkmqSOacWSxSjS7ki869q20XDe2W10 wwcnta7og1p8+mU9Vqysh1OC4RGfrEyBwwam4Y9HpOlTck5i9uhzO0HrRZySj8Apx6bj9htiEqVL hBDPR0osYkGiDKWULwK4INLM6ToWo2hoJe61hw5IxejhWTjlOPun8ZpTXrfBh5XfNej/v1/TiNJN PstGUB3bayBXKxQgkTwoWOUJozmDcJ9fnVOFFwo5ims4RslwLkZRai+EiNiTbrSCdCqAiPcjsRgl w2MaXRuHnZyBW/5MNjLOTuRzjTwMbNzkQ+kmP0o3+0ARdmMZTZGT2k4dNHTu6EGPHA96ZnuxXw+v 7mm7a+dWvXfZBmreoqZwGWRaz4kJEIEoRalQCFEQDbloBYm+PRH5AWExiqYbkutaWhspGNUGl59n 356ZWIn7/UBFZUDf80SOVBt9TZYSXo8Atcv4nJmu6Y5MMzMFaI+QmxJZKc5dVA165cQEmhOIQpRO F0K80/z+cJ+jEiTKSEp5L4C/hcuUxSgcHT5nEKCRw4Sz2oCilHJyBoEHn6jAu0s5PIQzesO5tYhw psMrRPhwE81bGIsg/QHA6uYZGZ/Zh5VBgl8jJdC+rYb8M7Iw7iwWpkiZmX0dGSy8uaDasrUzs+vP +VlPYERuBm6+qsXp9/uEELdGW6uoBYkKkFJ+AODE5oU9N6MSr8xmh4rNufDnyAjQesroYZksTJHh MuUqms14/W0WIlNgJmEmYUTpICEERRuPKsUqSJcCeCa4JBajYBr8Ph4CZHlGUWp5xBQPxfD3khDN nl8NWhPjxATiIRBClJYJIU6KJc9YBYl26e0aCrEYxYKe72mNAIXZPuvUTN0dUWvX8vnICDz8dAXe XlzDHhYiw8VXRUigmSidJ4R4NcJb97gsJkGiHKSU1wJ4+LHnd2LOfN4stwdV/mAqAa9X4PS8DFxz qT2xfUxtjA2Zfb2qAXMXVuP9j+t4jcgG/slSZJssbdurj+57aVaWmBtrm2MWJCrw7oe3n7j0wzpa T+LEBJQTILPqIw9Jw+jhmTj+KOdtsFUOIMoCFhTXYt6iat2PX5S38uVMIGoCAuL+opnZYS2wW8s0 LkGizPPyS5dCILe1gvg8EzCTABlAnDE0ExPOZsu85lzJ6ek7S2qwoyLQ/BR/ZgKqCASE39OvaNa+ a+IpwARBKjsXQhbGUwm+lwnESsDrAY45PB3DT8nAicck76jpvY/r9FhQX5XU87RcrA8T3xczAQEx t2hm9pkxZ/D7jXELUm6u9Hq6lv0MIKKY6fFWmO9nAi0RoP1Mg0/I0GMFDTootaXLEub4N6sasPjD Wn1tqLKKR0MJ07EubIgATi2ambMg3qrHLUhUgSEFZTcJyCnxVobvZwJmEdinowcn/TEdJx+brscR Mitfu/MhH3sffV6H95bXYcs2ttm2uz+4fJ3AqsUzswcBIu6IY6YIUu5Zazt4UtPWA+AJfX5CHUeA zMePPCQVfzw8HUcflgba5+SmRNNxH6+ow8df1INHQm7queSoqwCuKJqZ85QZrTVFkKgiQwrKHhGQ 15hRKc6DCagk0LO7F4f2T8XB/VP10VOXfZzjcZsct67+sRG0FvTFtw1Y84u7I92q7EfO2xEEttZI /37LC3vWmlEb0wRpeP6mXn4R+AGA14yKcR5MwCoC7dtpOLB3CvockIJ+vVLQM8eD7vt6kZZm2tcj ZFPKNvvx60Yfftnow/c/NYBiMW36jafhQsLig84kIMUdiwuz7zKrcqZ+4/IKSl8GcJ5ZleN8mICd BMhIonMnD9q1FaBpP4p+2yZT6EKV4hWgDbspv//8CgRoszjg+X2wVVsndW8IdQ0S9fUSOysD+nTb zqqAbo69dbsfPtYeO7uXy46fQJVX+vdfUNhze/xZNeVg7mjG438Afs8EAKYKnVmN5XyYQDQEaPqM /nNiAkxgbwISeMpMMaISTF3dXfxaz29EFBFl924iH2ECTIAJMAEXEGjwavIhs+tpqiBR5aSG+8yu JOfHBJgAE2ACziEgIF5ZOL07WVabmkwXpMXTcz4A5BJTa8mZMQEmwASYgFMI+ODX7lZRGdMFiSop Ie5UUVnOkwkwASbABGwmIOTL8fqsa6kFSgRpycyc9wEsbalQPs4EmAATYAKuJODzBwL/UlVzJYJE lZXAZFWV5nyZABNgAkzAFgKvFBf2/ElVycoE6fdR0iJVFed8mQATYAJMwFICDR6pKV2OUSZIhEnT cFvTYMlSaFwYE2ACTIAJmE5APrWwsNta07MNylCpIC2anvM5gFlB5fFbJsAEmAATcB+BGq8vVdna kYFDqSBRIR4/bgfgMwrkVybABJgAE3AXASnwyILZXcpU11q5IC2clbMaAi+obgjnzwSYABNgAkoI bGvwND6gJOdmmSoXJCrP25hCo6TqZmXzRybABJgAE3A4ASlw97JX9y+3opqWCBIN9SQER5S1oke5 DCbABJiAWQQEfqxon/2YWdm1lo8lgkSVyKqlEOeitLUK8XkmwASYABNwBgEZwC0rnhSNVtXGMkGa Ny+nRorA361qGJfDBJgAE2ACsROQwHtLCnPmxJ5D9HdaJkhUtZP757wACTIF58QEmAATYALOJeD3 yMD1VlfP8kB6eWPLjoaUH5sdi8lqcFweE2ACTCBRCUghH18yo/vVVrfP0hESNW7xjOzPADxrdUO5 PCbABJgAE2idgAS2BOobyMuO5clyQaIWpkpBjd1heWu5QCbABJgAEwhPQOC24jd62fL32RZBml+Y vQVC/F94KnyWCTABJsAErCQgpPzk5P7Zts1g2SJIBPik/t2eAPS1JCt5c1lMgAkwASYQmoDPr8kr J08WgdCn1R+13KghuEmDx244VJMaWd15g4/zeybABJgAE7CYgJBTFs/o/leLS92jONtGSFSLpTN6 fA2IaXvUiD8wASbABJiA1QTWeVI9SmMdRdIgWwWJKphZKwmC0hgbkYDga5gAE2ACyUpACnH1wpe6 2e5v1HZBIg8Omha4nAP5JetXgdvNBJiAzQReXjIje77NddCLt12QqBaLpvdYDOBJJwDhOjABJsAE kojAJq/0X+eU9jpCkAhGXV3KzQB+dQoYrgcTYAJMINEJSImrFxT23O6UdjpGkD6c26VSCslTd055 MrgeTIAJJDgBMd1q56mtAXWMIFFFl8zovhCQj7dWaT7PBJgAE2AC8RAQpV7p+3M8Oai411GCRA30 pHnIDv4HFY3lPJkAE2ACTABSQF7qpKk6o08cJ0hkehjQtAsB+IxK8isTYAJMgAmYRUD+t2hmzgKz cjMzH8cJEjVu6fRun0DIe8xsKOfFBJgAE0h2AlJgdWatsNUbQ7g+cKQgUYX9m3P+CYmPwlWezzEB JsAEmEDEBBoAnEd7PyO+w+ILHStIxcXCJ/2YIIByi5lwcUyACTCBxCMg8dclM3K+cHLDHCtIBG3J 7JxfpBRXOBkg140JMAEm4AICby8uzH7E6fV0tCARvMWF2a8L4Amng+T6MQEmwAScSUCU+j24GBDS mfXbXSvHCxJVtVr6b4TEl7urze+YABNgAkwgAgI+aHJc8Ws5WyO41vZLXCFIywt71krNkw+gwnZi XAEmwASYgEsISIhbF0/P+cAl1YUrBIlgLpmx789CyIvYK7hbHi2uJxNgAjYTmLNkZrepNtchquJd I0jUqqIZ3d+EkA9G1UK+mAkwASaQbAQEfhQy/RI3rBsFd42rBIkq3imQc6uUgsJVcGICTIAJMIG9 CVSLgHZOUWEn1y1xuE6QCguFPw0YD4Ff9u4HPsIEmAATSGoCUgAXFRV2W+lGCq4TJII8vzB7SwCB MwE4dsexGx8GrjMTYALuJiCBfxXNzJnl1la4UpAI9tIZPb6WQlzMRg5uffS43kyACZhKQMq3Th6Q fYepeVqcmbC4PNOLyysoux2Qd5meMWfIBJgAE3APgZVCpp/kxnWjYMSuFyRAiryCspfIaWBww/g9 E2ACTCAZCEhgS8AXOKJ4do8Nbm+va6fsdoMXspMsvxSQH+8+xu+YABNgAklBoEZq2qhEECPqrQQY ITU9dCPzy7o0CnwkIfsmxWPIjWQCTCDZCUgJed6Smd1fSxQQCTBCauoKsrzzSd9IGr4mSudwO5gA E2ACLREQErclkhhROxNmhGR02uBxm/6oBQJLAGQax/iVCTABJpBIBKSQjy+Z0f3qRGoTtSVhRkhG x1D484CU4ynorHGMX5kAE2ACCUTglX0COdckUHt2NSXhBIlatrSw+1wJ/IX3KO3qZ37DBJhAQhCQ S8iIizzWJERzmjUi4absgts3NL/0b1Lg3uBj/J4JMAEm4FICnwqZPtzte43CsU/IEZLR4KLCnPsE xP3GZ35lAkyACbiUwKpUKc5IZDGifknoEVLTg6dvnH0cwJUufRC52kyACSQzAYFf/I2BExNlr1G4 rkzoEVJTw2njbPafAbwSDgSfYwJMgAk4kMAGf8A/NBnEiNgnwQip6RHLz5eebaL0VQFR4MCHjqvE BJgAE2hGQJZByNzFM3r80OxEwn5MGkGiHjzyCpnSvrxsuhAYk7A9yg1jAkzA9QRog78mcErRjJzv XN+YKBqQBFN2u2mseFI07oPy8QJi7u6j/I4JMAEm4BwCuhhJLS/ZxIh6IKkEiRpcWDiwoaPcni8l ZjvnEeSaMAEmwAR0ApulRwx2a8TXePswqabsgmHl5kqvZ99NL0PKscHH+T0TYAJMwB4CotTjl3kL Z+Wstqd8+0tNuhGSgby4WPg6BbpRDKWXjWP8ygSYABOwicB6v/SdksxiRNyTVpCo8eR+o5PMpjDo /7PpIeRimQATSHICAuInvyZOLi7s+VOSo0ges+/wHS3FkLFl9wiJv4W/js8yASbABEwkIMU3fnhG FBd23WRirq7NKqlHSLt7TcglM3JuFVL8lR2y7qbC75gAE1BIQOKj+pSGXBaj3YyT1qhhN4I93w0Z WzpRSDwBwLPnGf7EBJgAEzCNwLuZtThn3rycGtNyTICMWJBCdOKQgo2jBASFBc4KcZoPMQEmwARi JiAgnivv0O1K2hcZcyYJeiMLUgsdS5FnRSAwTwBdWriEDzMBJsAEoiQg/7l4Zs4dgJBR3pgUl7Mg henm3Pz1fb3CO19C9g1zGZ9iAkyACbRGwC+Aq4pm5jzV2oXJfJ4FqZXezx1f2tnjxxwAJ7ZyKZ9m AkyACYQiUCGAsUUzcxaEOsnHdhNgK7vdLEK+K34tZ2tqZfVQ3kAbEg8fZAJMIDyBtQGPOIHFKDwk 4yyPkAwSEbzmFZTeCuBfyRS2IwIsfAkTYAIhCYjlnkZx1sI53X4LeZoP7kWABWkvJOEPDMkvPVsI vAigTfgr+SwTYAJJS0DI51N31vxp/vx+9UnLIIaGsyDFAG342E2DAlLOYWOHGODxLUwgsQn4hJA3 Fs3o/p/Ebqaa1rEgxcj1xAm/dEzzpbwK4NQYs+DbmAATSCACehwjTRQUTc8uTqBmWdoUNmqIEfey V/cv7ySzzwBwL7sbihEi38YEEoWAxOfw4WgWo/g6lEdI8fHT7x6aX3a6FPIFAPuYkB1nwQSYgIsI CCEeTdlZdROvF8XfaSxI8TPUcxiev6mXH4GZEDjKpCw5GybABJxNoAoCf1o8I+cVZ1fTPbXjKTuT +mphYbe1qVXVJ9KvJZ7CMwkqZ8MEnEtgpcePo1mMzO0gHiGZy1PPbejYjWdKKZ7hKTwFcDlLJmAz ASnk44GMhhuLn+9VZ3NVEq54FiRFXZo7ZkMPzau9LIBTFBXB2TIBJmAtge1SYuKSwhxyJcZJAQGe slMAlbIsnt1jwz4yOw8Q/wAku5lXxJmzZQIWEVgqJA5nMVJLm0dIavnquQ8bV3pUIACywhtgQXFc BBNgAuYRqBPAbScOyH548mQRMC9bzikUARakUFQUHDsuf31Gpua5FxLXsi88BYA5SyZgNgGJLwFx /uLC7FVmZ835hSbAghSai7KjeQVlgwXw5P+3d26xVVRRGP7/PbW2oqYgSIGC8a6QoPLgJYKppYL1 Fi+p0AcUrw/6oDHxHhU1XuOLiYmJqVGjRvAIGjERQ9GCGNEgmGgUlYhSORRRqQhKy5lZZho1EUs9 9zMz5386ObPXWnutb03yZ86Zvbe2HSoZYgUWgUIJDBj48K8NjQ/rVNdCUebmL0HKjVdRrAeflliz ALBbANQUJaiCiIAIFEzAgA/DPS8CAAAGbElEQVRovF5PRQWjzCuABCkvbMVxap275RQL2AlgWnEi KooIiECeBHYCvGvG5Man9V9RngSL4CZBKgLEQkI0N1uNO7z3ZsIeAFBfSCz5ioAI5EOAS/2Mf0P4 Zmw+3vIpHgEJUvFYFhSp9bJtRwUueIa0mQUFkrMIiEC2BLbB7KYVqQmLsnWQXWkJSJBKyzfH6MaW Oen5ND4OYHSOzjIXARHIjoABfK7GMre+k5r4S3YusioHAQlSOSjnOMfs9p5RGboHAV6vlx5yhCdz ERiewDrS3di1qHHN8GYarQQBCVIlqGc55+BLDz6fBDEjSxeZiYAIDE3gJxrunj5lXKdeWhgaUBSu SpCi0IVhczC2XJ6eS/BRAJOGNdWgCIjAvgQGQDzl9/c/2P3GkX37Dup7tAhIkKLVj/1mE65dGgHv JiNuB9CwX0MNiIAI/E3gdd/827pTEzf+fUGf0SYgQYp2f/6TXXNHerTn2/0ArgN4wH8MdEEEqpwA zT6C5+7QceLxuxEkSPHr2WDGze09x3j0FgDoAKBd22PaR6VdVAJfkLina9G41wFaUSMrWFkISJDK grl0k8zs6JmKjHsI5AWlm0WRRSDCBIjvYbxvlDW+lErRj3CmSu1/CEiQ/gdQXIZb5/SebkFwL4i2 uOSsPEWgQAKbaXx0JH55NpWaMlBgLLlHgIAEKQJNKGYKLe1bTyXtXgDnFzOuYolAhAh8R8MjI7Hj eQlRhLpShFQkSEWAGMUQ4aGAvo87SVys/5ii2CHllAeBrwh7fEfD+Bd1LEQe9GLgIkGKQZMKSfHs 9i3HO8dbYZgHoLaQWPIVgQoR+JjEY9NPHPeGFrVWqANlmlaCVCbQlZ7mnLmbxwfm3QzjdVrHVOlu aP4sCAQElhn4xIpXx72Xhb1MEkBAgpSAJuZSwqx5vSP8Absy3OUYwHG5+MpWBMpAYDdgL4D25IpF TV+XYT5NESECEqQINaOcqSxYYG7lF73nerAbDZgNwCvn/JpLBP5FgPjGjM8EA3s6tcXPv8hU1RcJ UlW1e+hiW9vTkwLyKsKu1n55QzPS1ZIQ6AfwGh07uxY2rtRi1pIwjlVQCVKs2lXaZMOnplVf9s5G YNeSdqG2Jiot7yqO/hkMz9bAf1HnEVXxXTBE6RKkIaDoEjCzY9tY8zPzSV4Dw7FiIgIFEtgF8FWY 37ki1fRhgbHknlACEqSENrZ4ZYXHX2ydQeIKGi41YGTxYitSwgkEAFbC7OXa2rrU2y8ftjPh9aq8 AglIkAoEWE3ubW3fHDhwyIg2g3UQvBBAfTXVr1qzJGBYC+AV52UWLl84KZ2ll8xEABIk3QR5ETjz ou2H1B04cB7Jyww4D8CIvALJKQkEDLCPaG4JAre4a/HYb5NQlGooPwEJUvmZJ27G8PDAg+iFr45f YkAbgTGJK1IF7UsgA9gqI5cGe4PXupc0/bCvgb6LQK4EJEi5EpP9sATa28372UufxoAXwHg+aFOH ddBgnAj8BGAZzN4i6pd1pUb9GqfklWv0CUiQot+jWGfYfOkPTTU1nGVw5xhspp6eYtXO8EiHNQSW +84tH+2PXavzhmLVv9glK0GKXcvim3C4zmn1hvRJ8F2rITgL5HTtqxepfvowrIfDKgZ8NwNvZXfq 8F2RylDJJJqABCnR7Y12ceHPezu89FTzeRaIGQBOBzAh2lknKrvdMKwDsdrI9/v/qFn9wZtjfktU hSomVgQkSLFqV/KTbWnvmeCcdxrMzjDwVAAnAzg0+ZWXvMKMARsIfGLAGs+CNXu3T/i8u5uZks+s CUQgSwISpCxByaxSBIwtc348CgimATaNNihQk7Xn3rD92EmzL438FMT6gO7Tg3cHny1dOv73Yb00 KAIVJiBBqnADNH1+BMJ1ULUH+ZO9wJ9ixAk0d7yZHQ3iaAB1+UWNlZcBCF+13mjARho2mLPPXcAN Xanxm2NViZIVgb8ISJB0KySMgHHW3HSTH7hjBp+iDEcYrYmGpsHvxMSY/AQY7oS9zYAewLYQDD83 G7DJkRsz9f2bup8/ck/CmqdyqpyABKnKb4BqLL95/qa62j21Y3xzjQDHGvwxNDfKYA2ObDCzBpg1 GFy9ozWY8QA4Oxhm9QT/efraz75+vxMIxQTh9gUg+mDYS2CXGcKxPwJaH8k+GnfArC8g+hyw3Rx+ 9PZie+DqtmqNTzXemar5T7boKrYfCqI6AAAAAElFTkSuQmCC"/></symbol><symbol viewBox="0 0 24 24" id="webpack" xmlns="http://www.w3.org/2000/svg"><path d="M19.376 15.988l-7.709 4.45-7.708-4.45V7.087l7.708-4.45 7.709 4.45z" fill="#fff" fill-opacity=".785" stroke-width="0"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18.21 0 .41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.939v2.104h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07l7-3.94zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#1c78c0"/></symbol><symbol viewBox="0 0 24 24" id="wolframlanguage" xmlns="http://www.w3.org/2000/svg"><title>wolframLanguage</title><g transform="scale(.12121)" fill="none" fill-rule="evenodd"><circle cx="99.197" cy="98.946" r="83.28" fill="#212121" stroke-width=".841"/><path d="M182.529 98.828a83.406 83.406 0 0 1-39.14 70.721.064.064 0 0 1-.038.019l-28.62-35.665 23.71 2.612s11.385 1.177 13.978 0c2.373-.938 15.175-18.963 15.175-18.963s-36.75-23.23-49.312-36.032c1.434-21.575-1.656-50.269-1.656-50.03-9.251 9.234-10.429 10.669-19.68 19.203-4.028-13.04-5.923-17.547-9.95-30.588-12.104 9.95-21.337 26.799-27.977 46.48a78.68 78.68 0 0 0-4.23 5.094 109.774 109.774 0 0 0-2.667 3.66 114.558 114.558 0 0 0-5.132 8.002 172.555 172.555 0 0 0-3.403 6.051c-7.706 14.475-14.034 31.066-19.515 46.001a.858.858 0 0 1-.092-.184c-14.988-30.912-9.502-67.85 13.822-93.072 23.325-25.223 59.723-33.575 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z" fill="#e53935"/><path d="M101.452 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.04.477 11.845.238 18.006-.479 32.481-3.55-3.568-10.668-8.074-10.668-8.074zm-27.737 40.778s-6.64-4.029-11.624-4.728c1.435-3.329 5.223-7.596 6.18-8.773-1.913.699-15.653 6.86-17.087 12.084a74.804 74.804 0 0 1 11.385 3.79 35.993 35.993 0 0 0-8.774 20.158s21.815-3.33 38.185-1.196c.283.168.609.251.938.24l8.534.239 27.111 45.136.221.35c-.037.018-.055.037-.073.037-51.133 18.485-88.085-15.543-95.976-27.443.034-.102.058-.206.074-.313 7.1-30.017 15.855-65.939 30-76.552 7.356-12.82 9.49-31.783 22.751-41.734 3.33 9.951 8.553 30.588 12.103 40.539 15.653 15.652 39.361 35.094 55.234 43.15 1.656.956 3.79 7.596 3.79 7.596l-6.401 8.056-68.276-6.879a54.462 54.462 0 0 0-4.58-.183 86.848 86.848 0 0 0-14.144 1.36c3.311-8.295 10.43-14.935 10.43-14.935zm22.054-8.774c3.789-.46 7.817.956 12.323 3.568 4.267-1.195 4.745-1.434 9.013-2.612-5.463-4.028-11.386-8.295-19.442-7.118a47.249 47.249 0 0 0-1.894 6.162z" fill="#fff" stroke-width=".936"/></g></symbol><symbol viewBox="0 0 24 24" id="word" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2h1z" fill="#01579b"/></symbol><symbol viewBox="0 0 24 24" id="xaml" xmlns="http://www.w3.org/2000/svg"><path d="M18.93 12l-3.47 6H8.54l-3.47-6 3.47-6h6.92l3.47 6m4.84 0l-4.04 7L18 18l3.46-6L18 6l1.73-1 4.04 7M.23 12l4.04-7L6 6l-3.46 6L6 18l-1.73 1-4.04-7z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="xml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="yaml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="yang" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 289.99999 290.00001" id="yarn" xmlns="http://www.w3.org/2000/svg"><path d="M250.733 218.418c-12.39 2.943-18.661 5.653-33.993 15.641-24.004 15.487-50.176 22.688-50.176 22.688s-2.168 3.252-8.44 4.723c-10.84 2.633-51.647 4.878-55.364 4.956-9.988.077-16.105-2.555-17.809-6.66-5.188-12.388 7.434-17.809 7.434-17.809s-2.788-1.703-4.414-3.252c-1.471-1.47-3.02-4.413-3.484-3.33-1.936 4.724-2.943 16.261-8.13 21.45-7.125 7.2-20.598 4.8-28.573.619-8.75-4.646.62-15.564.62-15.564s-4.724 2.788-8.518-2.942c-3.407-5.266-6.582-14.248-5.73-25.32 1.084-12.777 15.176-25.011 15.176-25.011s-2.477-18.661 5.653-37.787c7.356-17.422 27.179-31.437 27.179-31.437s-16.648-18.352-10.454-35c4.027-10.84 5.653-10.763 6.97-11.227 4.645-1.781 9.136-3.717 12.466-7.356 16.648-17.964 37.864-14.557 37.864-14.557s9.911-30.431 19.203-24.469c2.865 1.859 13.163 24.778 13.163 24.778s10.996-6.426 12.235-4.026c6.659 12.931 7.433 37.632 4.49 52.654-4.955 24.778-17.344 38.096-22.3 46.459-1.161 1.936 13.319 8.053 22.456 33.373 8.44 23.152.929 42.587 2.245 44.756.232.387.31.542.31.542s9.679.774 29.114-11.228c10.376-6.427 22.688-13.628 36.703-13.783 13.55-.232 14.247 15.719 4.104 18.12z" fill="#2c8ebb" stroke-width=".774"/></symbol><symbol viewBox="0 0 24 24" id="zip" xmlns="http://www.w3.org/2000/svg"><path d="M14 17h-2v-2h-2v-2h2v2h2m0-6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2m5-4H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#afb42b"/></symbol></svg>
diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png
index ca30b459019..12525056939 100644
--- a/app/assets/images/gitlab_logo.png
+++ b/app/assets/images/gitlab_logo.png
Binary files differ
diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
index f4e19b67008..c650177c960 100644
--- a/app/assets/images/logo.svg
+++ b/app/assets/images/logo.svg
@@ -1,26 +1,10 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
- <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
- <title>Slice 1</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
- <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
- <g id="Page-1" sketch:type="MSShapeGroup">
- <g id="Fill-1-+-Group-24">
- <g id="Group-24">
- <g id="Group">
- <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
- <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
- <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
- <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
- <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
- <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
- <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
- </g>
- </g>
- </g>
- </g>
- </g>
- </g>
+<svg width="200" height="192" class="tanuki-logo" viewBox="0 0 50 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path class="tanuki-shape tanuki" d="m49.014 19-.067-.18-6.784-17.696a1.792 1.792 0 0 0-3.389.182l-4.579 14.02H15.651l-4.58-14.02a1.795 1.795 0 0 0-3.388-.182l-6.78 17.7-.071.175A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869 10.394-7.779.029-.022a12.595 12.595 0 0 0 4.182-14.554Z"
+ fill="#E24329"/>
+ <path class="tanuki-shape right-cheek" d="m49.014 19-.067-.18a22.88 22.88 0 0 0-9.12 4.103L24.931 34.187l9.485 7.167 10.393-7.779.03-.022a12.595 12.595 0 0 0 4.175-14.554Z"
+ fill="#FC6D26"/>
+ <path class="tanuki-shape chin" d="m15.414 41.354 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869-9.484-7.167-9.51 7.167Z"
+ fill="#FCA326"/>
+ <path class="tanuki-shape left-cheek" d="M10.019 22.923a22.86 22.86 0 0 0-9.117-4.1L.832 19A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 9.491-7.167L10.02 22.923Z"
+ fill="#FC6D26"/>
</svg>
diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif
deleted file mode 100644
index 3f4ef31947b..00000000000
--- a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif
deleted file mode 100644
index 387628f831c..00000000000
--- a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_footer_logo.gif b/app/assets/images/mailers/gitlab_footer_logo.gif
deleted file mode 100644
index 3f4ef31947b..00000000000
--- a/app/assets/images/mailers/gitlab_footer_logo.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_header_logo.gif b/app/assets/images/mailers/gitlab_header_logo.gif
deleted file mode 100644
index 387628f831c..00000000000
--- a/app/assets/images/mailers/gitlab_header_logo.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_header_logo.png b/app/assets/images/mailers/gitlab_header_logo.png
deleted file mode 100644
index 35ca1860887..00000000000
--- a/app/assets/images/mailers/gitlab_header_logo.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_logo.png b/app/assets/images/mailers/gitlab_logo.png
new file mode 100644
index 00000000000..12525056939
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_logo.png
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_logo_black_text.png b/app/assets/images/mailers/gitlab_logo_black_text.png
new file mode 100644
index 00000000000..ed8f05a633e
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_logo_black_text.png
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_tanuki_2x.png b/app/assets/images/mailers/gitlab_tanuki_2x.png
deleted file mode 100644
index 551dd6ce2ce..00000000000
--- a/app/assets/images/mailers/gitlab_tanuki_2x.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/in_product_marketing/gitlab-logo-gray-rgb.png b/app/assets/images/mailers/in_product_marketing/gitlab-logo-gray-rgb.png
deleted file mode 100644
index 31083af512e..00000000000
--- a/app/assets/images/mailers/in_product_marketing/gitlab-logo-gray-rgb.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png
deleted file mode 100644
index 1e0e2ed73ce..00000000000
--- a/app/assets/images/msapplication-tile.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png
deleted file mode 100644
index 516dc2f4710..00000000000
--- a/app/assets/images/touch-icon-ipad-retina.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png
deleted file mode 100644
index b2093d015b8..00000000000
--- a/app/assets/images/touch-icon-ipad.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png
deleted file mode 100644
index 438654e0d20..00000000000
--- a/app/assets/images/touch-icon-iphone-retina.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png
deleted file mode 100644
index e5f87fbbcf6..00000000000
--- a/app/assets/images/touch-icon-iphone.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/twitter_card.jpg b/app/assets/images/twitter_card.jpg
new file mode 100644
index 00000000000..6b998ab731c
--- /dev/null
+++ b/app/assets/images/twitter_card.jpg
Binary files differ
diff --git a/app/assets/javascripts/access_tokens/components/projects_token_selector.vue b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
index a746f62b3a1..4843c52fcbb 100644
--- a/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
+++ b/app/assets/javascripts/access_tokens/components/projects_token_selector.vue
@@ -148,7 +148,7 @@ export default {
</template>
<template #dropdown-footer>
<gl-intersection-observer v-if="projects.pageInfo.hasNextPage" @appear="loadMoreProjects">
- <gl-loading-icon v-if="isLoadingMoreProjects" size="md" />
+ <gl-loading-icon v-if="isLoadingMoreProjects" class="gl-mb-3" size="sm" />
</gl-intersection-observer>
</template>
</gl-token-selector>
diff --git a/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue b/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue
new file mode 100644
index 00000000000..7f6e5dc4f35
--- /dev/null
+++ b/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue
@@ -0,0 +1,51 @@
+<script>
+import { GlListbox } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
+
+export default {
+ name: 'BackgroundMigrationsDatabaseListbox',
+ i18n: {
+ database: s__('BackgroundMigrations|Database'),
+ },
+ components: {
+ GlListbox,
+ },
+ props: {
+ databases: {
+ type: Array,
+ required: true,
+ },
+ selectedDatabase: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selected: this.selectedDatabase,
+ };
+ },
+ methods: {
+ selectDatabase(database) {
+ visitUrl(setUrlParams({ database }));
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center" data-testid="database-listbox">
+ <label id="label" class="gl-font-weight-bold gl-mr-4 gl-mb-0">{{
+ $options.i18n.database
+ }}</label>
+ <gl-listbox
+ v-model="selected"
+ :items="databases"
+ right
+ :toggle-text="selectedDatabase"
+ aria-labelledby="label"
+ @select="selectDatabase"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/background_migrations/index.js b/app/assets/javascripts/admin/background_migrations/index.js
new file mode 100644
index 00000000000..4ddd8f17c9a
--- /dev/null
+++ b/app/assets/javascripts/admin/background_migrations/index.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import * as Sentry from '@sentry/browser';
+import Translate from '~/vue_shared/translate';
+import BackgroundMigrationsDatabaseListbox from './components/database_listbox.vue';
+
+Vue.use(Translate);
+
+export const initBackgroundMigrationsApp = () => {
+ const el = document.getElementById('js-database-listbox');
+
+ if (!el) {
+ return false;
+ }
+
+ const { selectedDatabase } = el.dataset;
+ let { databases } = el.dataset;
+
+ try {
+ databases = JSON.parse(databases).map((database) => ({
+ value: database,
+ text: database,
+ }));
+ } catch (e) {
+ Sentry.captureException(e);
+ }
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(BackgroundMigrationsDatabaseListbox, {
+ props: {
+ databases,
+ selectedDatabase,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/admin/statistics_panel/components/app.vue b/app/assets/javascripts/admin/statistics_panel/components/app.vue
index 1f0db422807..f250bdae4f5 100644
--- a/app/assets/javascripts/admin/statistics_panel/components/app.vue
+++ b/app/assets/javascripts/admin/statistics_panel/components/app.vue
@@ -29,7 +29,7 @@ export default {
<div class="gl-card">
<div class="gl-card-body">
<h4>{{ __('Statistics') }}</h4>
- <gl-loading-icon v-if="isLoading" size="md" class="my-3" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="my-3" />
<template v-else>
<p
v-for="statistic in getStatistics(statisticsLabels)"
diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue
index 929f5d10956..37a6ea16018 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_table.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue
@@ -178,8 +178,8 @@ export default {
serverErrorMessage: '',
isErrorAlertDismissed: false,
sort: 'STARTED_AT_DESC',
- statusFilter: [],
- filteredByStatus: '',
+ statusFilter: ALERTS_STATUS_TABS[0].filters,
+ filteredByStatus: ALERTS_STATUS_TABS[0].status,
alerts: {},
alertsCount: {},
sortBy: 'startedAt',
@@ -283,13 +283,17 @@ export default {
<paginated-table-with-search-and-tabs
:show-error-msg="showErrorMsg"
:i18n="$options.i18n"
- :items="alerts.list || []"
+ :items="
+ alerts.list || [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
+ "
:page-info="alerts.pageInfo"
:items-count="alertsCount"
:status-tabs="$options.statusTabs"
:track-views-options="$options.trackAlertListViewsOptions"
:server-error-message="serverErrorMessage"
- :filter-search-tokens="['assignee_username']"
+ :filter-search-tokens="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ 'assignee_username',
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
filter-search-key="alerts"
@page-changed="pageChanged"
@tabs-changed="statusChanged"
@@ -305,7 +309,11 @@ export default {
<template #table>
<gl-table
class="alert-management-table"
- :items="alerts ? alerts.list : []"
+ :items="
+ alerts
+ ? alerts.list
+ : [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
+ "
:fields="$options.fields"
:show-empty="true"
:busy="loading"
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
index 902bad780ad..1f970ef1846 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -196,7 +196,7 @@ export default {
.then(
({ data: { httpIntegrationResetToken, prometheusIntegrationResetToken } = {} } = {}) => {
const [error] =
- httpIntegrationResetToken?.errors || prometheusIntegrationResetToken?.errors;
+ httpIntegrationResetToken?.errors || prometheusIntegrationResetToken.errors;
if (error) {
return createFlash({ message: error });
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
index d4f4f244759..babcdea935d 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
@@ -3,8 +3,6 @@
mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
errors
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
integration {
...HttpIntegrationItem
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql
index caa258e0848..05bf8eab524 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql
@@ -1,10 +1,8 @@
#import "../fragments/http_integration_item.fragment.graphql"
-mutation destroyHttpIntegration($id: ID!) {
+mutation destroyHttpIntegration($id: AlertManagementHttpIntegrationID!) {
httpIntegrationDestroy(input: { id: $id }) {
errors
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
integration {
...HttpIntegrationItem
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
index 2f30f9abb5c..65245bfb914 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
@@ -1,10 +1,8 @@
#import "../fragments/http_integration_item.fragment.graphql"
-mutation resetHttpIntegrationToken($id: ID!) {
+mutation resetHttpIntegrationToken($id: AlertManagementHttpIntegrationID!) {
httpIntegrationResetToken(input: { id: $id }) {
errors
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
integration {
...HttpIntegrationItem
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql
index 8f34521b9fd..99179fca1f9 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql
@@ -1,6 +1,6 @@
#import "../fragments/integration_item.fragment.graphql"
-mutation resetPrometheusIntegrationToken($id: ID!) {
+mutation resetPrometheusIntegrationToken($id: IntegrationsPrometheusID!) {
prometheusIntegrationResetToken(input: { id: $id }) {
errors
integration {
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
index 2cf56613673..cc9e841ffb9 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
@@ -1,10 +1,12 @@
#import "../fragments/http_integration_item.fragment.graphql"
-mutation updateHttpIntegration($id: ID!, $name: String!, $active: Boolean!) {
+mutation updateHttpIntegration(
+ $id: AlertManagementHttpIntegrationID!
+ $name: String!
+ $active: Boolean!
+) {
httpIntegrationUpdate(input: { id: $id, name: $name, active: $active }) {
errors
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
integration {
...HttpIntegrationItem
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql
index 62761730bd2..95d72e4af91 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql
@@ -1,6 +1,10 @@
#import "../fragments/integration_item.fragment.graphql"
-mutation updatePrometheusIntegration($id: ID!, $apiUrl: String!, $active: Boolean!) {
+mutation updatePrometheusIntegration(
+ $id: IntegrationsPrometheusID!
+ $apiUrl: String!
+ $active: Boolean!
+) {
prometheusIntegrationUpdate(input: { id: $id, apiUrl: $apiUrl, active: $active }) {
errors
integration {
diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integration.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integration.query.graphql
index 7299e6836d4..5f6ab27cae9 100644
--- a/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integration.query.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integration.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql"
-query getHttpIntegration($projectPath: ID!, $id: ID) {
+query getHttpIntegration($projectPath: ID!, $id: AlertManagementHttpIntegrationID!) {
project(fullPath: $projectPath) {
id
alertManagementHttpIntegrations(id: $id) {
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index b2b033de75d..b151e1605da 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -7,6 +7,7 @@ import {
GlDropdownSectionHeader,
GlDropdownItem,
GlSearchBoxByType,
+ GlTruncate,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { filterBySearchTerm } from '~/analytics/shared/utils';
@@ -28,6 +29,7 @@ export default {
GlDropdownSectionHeader,
GlDropdownItem,
GlSearchBoxByType,
+ GlTruncate,
},
props: {
groupId: {
@@ -212,30 +214,29 @@ export default {
<gl-dropdown
ref="projectsDropdown"
class="dropdown dropdown-projects"
- toggle-class="gl-shadow-none"
+ toggle-class="gl-shadow-none gl-mb-0"
:loading="loadingDefaultProjects"
:show-clear-all="hasSelectedProjects"
show-highlighted-items-title
highlighted-items-title-class="gl-p-3"
+ block
@clear-all.stop="onClearAll"
@hide="onHide"
>
<template #button-content>
- <gl-loading-icon v-if="loadingDefaultProjects" class="gl-mr-2" />
- <div class="gl-display-flex gl-flex-grow-1">
- <gl-avatar
- v-if="isOnlyOneProjectSelected"
- :src="selectedProjects[0].avatarUrl"
- :entity-id="getEntityId(selectedProjects[0])"
- :entity-name="selectedProjects[0].name"
- :size="16"
- :shape="$options.AVATAR_SHAPE_OPTION_RECT"
- :alt="selectedProjects[0].name"
- class="gl-display-inline-flex gl-vertical-align-middle gl-mr-2"
- />
- {{ selectedProjectsLabel }}
- </div>
- <gl-icon class="gl-ml-2" name="chevron-down" />
+ <gl-loading-icon v-if="loadingDefaultProjects" class="gl-mr-2 gl-flex-shrink-0" />
+ <gl-avatar
+ v-if="isOnlyOneProjectSelected"
+ :src="selectedProjects[0].avatarUrl"
+ :entity-id="getEntityId(selectedProjects[0])"
+ :entity-name="selectedProjects[0].name"
+ :size="16"
+ :shape="$options.AVATAR_SHAPE_OPTION_RECT"
+ :alt="selectedProjects[0].name"
+ class="gl-display-inline-flex gl-vertical-align-middle gl-mr-2 gl-flex-shrink-0"
+ />
+ <gl-truncate :text="selectedProjectsLabel" class="gl-min-w-0 gl-flex-grow-1" />
+ <gl-icon class="gl-ml-2 gl-flex-shrink-0" name="chevron-down" />
</template>
<template #header>
<gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
diff --git a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue
index 1a3544e7677..6ac1bce4032 100644
--- a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue
+++ b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue
@@ -1,6 +1,6 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
-import { flatten, isEqual } from 'lodash';
+import { flatten, isEqual, keyBy } from 'lodash';
import createFlash from '~/flash';
import { sprintf, s__ } from '~/locale';
import { METRICS_POPOVER_CONTENT } from '../constants';
@@ -28,6 +28,23 @@ const fetchMetricsData = (reqs = [], path, params) => {
);
};
+const extractMetricsGroupData = (keyList = [], data = []) => {
+ if (!keyList.length || !data.length) return [];
+ const kv = keyBy(data, 'identifier');
+ return keyList.map((id) => kv[id] || null).filter((obj) => Boolean(obj));
+};
+
+const groupRawMetrics = (groups = [], rawData = []) => {
+ return groups.map((curr) => {
+ const { keys, ...rest } = curr;
+ return {
+ data: extractMetricsGroupData(keys, rawData),
+ keys,
+ ...rest,
+ };
+ });
+};
+
export default {
name: 'ValueStreamMetrics',
components: {
@@ -52,13 +69,24 @@ export default {
required: false,
default: null,
},
+ groupBy: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
metrics: [],
+ groupedMetrics: [],
isLoading: false,
};
},
+ computed: {
+ hasGroupedMetrics() {
+ return Boolean(this.groupBy.length);
+ },
+ },
watch: {
requestParams(newVal, oldVal) {
if (!isEqual(newVal, oldVal)) {
@@ -76,6 +104,11 @@ export default {
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
.then((data) => {
this.metrics = this.filterFn ? this.filterFn(data) : data;
+
+ if (this.hasGroupedMetrics) {
+ this.groupedMetrics = groupRawMetrics(this.groupBy, this.metrics);
+ }
+
this.isLoading = false;
})
.catch(() => {
@@ -86,14 +119,35 @@ export default {
};
</script>
<template>
- <div class="gl-display-flex gl-flex-wrap" data-testid="vsa-metrics">
+ <div class="gl-display-flex gl-mt-6" data-testid="vsa-metrics">
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
- <metric-tile
- v-for="metric in metrics"
- v-show="!isLoading"
- :key="metric.identifier"
- :metric="metric"
- class="gl-my-6 gl-pr-9"
- />
+ <template v-else>
+ <div v-if="hasGroupedMetrics" class="gl-flex-direction-column">
+ <div
+ v-for="group in groupedMetrics"
+ :key="group.key"
+ class="gl-mb-7"
+ data-testid="vsa-metrics-group"
+ >
+ <h4 class="gl-my-0">{{ group.title }}</h4>
+ <div class="gl-display-flex gl-flex-wrap">
+ <metric-tile
+ v-for="metric in group.data"
+ :key="metric.identifier"
+ :metric="metric"
+ class="gl-mt-5 gl-pr-10"
+ />
+ </div>
+ </div>
+ </div>
+ <div v-else class="gl-display-flex gl-flex-wrap gl-mb-7">
+ <metric-tile
+ v-for="metric in metrics"
+ :key="metric.identifier"
+ :metric="metric"
+ class="gl-mt-5 gl-pr-10"
+ />
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/analytics/shared/constants.js b/app/assets/javascripts/analytics/shared/constants.js
index 2ac144ceb5e..38d05552783 100644
--- a/app/assets/javascripts/analytics/shared/constants.js
+++ b/app/assets/javascripts/analytics/shared/constants.js
@@ -55,4 +55,40 @@ export const METRICS_POPOVER_CONTENT = {
commits: {
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
},
+ 'time-to-restore-service': {
+ description: s__(
+ 'ValueStreamAnalytics|Median time an incident was open on a production environment in the given time period.',
+ ),
+ },
+ time_to_restore_service: {
+ description: s__(
+ 'ValueStreamAnalytics|Median time an incident was open on a production environment in the given time period.',
+ ),
+ },
+ 'change-failure-rate': {
+ description: s__(
+ 'ValueStreamAnalytics|Percentage of deployments that cause an incident in production.',
+ ),
+ },
+ change_failure_rate: {
+ description: s__(
+ 'ValueStreamAnalytics|Percentage of deployments that cause an incident in production.',
+ ),
+ },
};
+
+const KEY_METRICS_TITLE = s__('ValueStreamAnalytics|Key metrics');
+const KEY_METRICS_KEYS = ['lead_time', 'cycle_time', 'issues', 'commits', 'deploys'];
+
+const DORA_METRICS_TITLE = s__('ValueStreamAnalytics|DORA metrics');
+const DORA_METRICS_KEYS = [
+ 'deployment_frequency',
+ 'lead_time_for_changes',
+ 'time_to_restore_service',
+ 'change_failure_rate',
+];
+
+export const VSA_METRICS_GROUPS = [
+ { key: 'key_metrics', title: KEY_METRICS_TITLE, keys: KEY_METRICS_KEYS },
+ { key: 'dora_metrics', title: DORA_METRICS_TITLE, keys: DORA_METRICS_KEYS },
+];
diff --git a/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue b/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue
index 09dfcddcb73..dfe94aeb884 100644
--- a/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue
+++ b/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue
@@ -122,7 +122,7 @@ export default {
<div>
<h3>{{ $options.i18n.yAxisTitle }}</h3>
<gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
- {{ this.$options.i18n.loadUserChartError }}
+ {{ $options.i18n.loadUserChartError }}
</gl-alert>
<chart-skeleton-loader v-else-if="isLoading" />
<gl-alert v-else-if="!chartUserData.length" variant="info" :dismissible="false" class="gl-mt-3">
@@ -132,12 +132,12 @@ export default {
v-else
:option="options"
:include-legend-avg-max="true"
- :data="[
+ :data="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
{
name: $options.i18n.yAxisTitle,
data: chartUserData,
},
- ]"
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
/>
</div>
</template>
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 64812e52849..8d46ea76be1 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -93,6 +93,7 @@ const Api = {
groupNotificationSettingsPath: '/api/:version/groups/:id/notification_settings',
notificationSettingsPath: '/api/:version/notification_settings',
deployKeysPath: '/api/:version/deploy_keys',
+ secureFilePath: '/api/:version/projects/:project_id/secure_files/:secure_file_id',
secureFilesPath: '/api/:version/projects/:project_id/secure_files',
dependencyProxyPath: '/api/:version/groups/:id/dependency_proxy/cache',
@@ -857,6 +858,14 @@ const Api = {
});
},
+ tag(id, tagName) {
+ const url = Api.buildUrl(this.tagPath)
+ .replace(':id', encodeURIComponent(id))
+ .replace(':tag_name', encodeURIComponent(tagName));
+
+ return axios.get(url);
+ },
+
freezePeriods(id) {
const url = Api.buildUrl(this.freezePeriodsPath).replace(':id', encodeURIComponent(id));
@@ -970,6 +979,22 @@ const Api = {
return axios.get(url, { params: { per_page: DEFAULT_PER_PAGE, ...options } });
},
+ uploadProjectSecureFile(projectId, fileData) {
+ const url = Api.buildUrl(this.secureFilesPath).replace(':project_id', projectId);
+
+ const headers = { 'Content-Type': 'multipart/form-data' };
+
+ return axios.post(url, fileData, { headers });
+ },
+
+ deleteProjectSecureFile(projectId, secureFileId) {
+ const url = Api.buildUrl(this.secureFilePath)
+ .replace(':project_id', projectId)
+ .replace(':secure_file_id', secureFileId);
+
+ return axios.delete(url);
+ },
+
async updateNotificationSettings(projectId, groupId, data = {}) {
let url = Api.buildUrl(this.notificationSettingsPath);
diff --git a/app/assets/javascripts/api/integrations_api.js b/app/assets/javascripts/api/integrations_api.js
new file mode 100644
index 00000000000..692aae21a4f
--- /dev/null
+++ b/app/assets/javascripts/api/integrations_api.js
@@ -0,0 +1,21 @@
+import axios from '../lib/utils/axios_utils';
+import { buildApiUrl } from './api_utils';
+
+const JIRA_CONNECT_SUBSCRIPTIONS_PATH = '/api/:version/integrations/jira_connect/subscriptions';
+
+export function addJiraConnectSubscription(namespacePath, { jwt, accessToken }) {
+ const url = buildApiUrl(JIRA_CONNECT_SUBSCRIPTIONS_PATH);
+
+ return axios.post(
+ url,
+ {
+ jwt,
+ namespace_path: namespacePath,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`, // eslint-disable-line @gitlab/require-i18n-strings
+ },
+ },
+ );
+}
diff --git a/app/assets/javascripts/api/tags_api.js b/app/assets/javascripts/api/tags_api.js
new file mode 100644
index 00000000000..d4ee247ade6
--- /dev/null
+++ b/app/assets/javascripts/api/tags_api.js
@@ -0,0 +1,12 @@
+import axios from '../lib/utils/axios_utils';
+import { buildApiUrl } from './api_utils';
+
+const TAG_PATH = '/api/:version/projects/:id/repository/tags/:tag_name';
+
+export function getTag(id, tagName) {
+ const url = buildApiUrl(TAG_PATH)
+ .replace(':id', encodeURIComponent(id))
+ .replace(':tag_name', encodeURIComponent(tagName));
+
+ return axios.get(url);
+}
diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js
index 09995fad628..c362253f52e 100644
--- a/app/assets/javascripts/api/user_api.js
+++ b/app/assets/javascripts/api/user_api.js
@@ -10,6 +10,9 @@ const USER_PATH = '/api/:version/users/:id';
const USER_STATUS_PATH = '/api/:version/users/:id/status';
const USER_PROJECTS_PATH = '/api/:version/users/:id/projects';
const USER_POST_STATUS_PATH = '/api/:version/user/status';
+const USER_FOLLOW_PATH = '/api/:version/users/:id/follow';
+const USER_UNFOLLOW_PATH = '/api/:version/users/:id/unfollow';
+const CURRENT_USER_PATH = '/api/:version/user';
export function getUsers(query, options) {
const url = buildApiUrl(USERS_PATH);
@@ -69,3 +72,18 @@ export function updateUserStatus({ emoji, message, availability, clearStatusAfte
clear_status_after: clearStatusAfter,
});
}
+
+export function followUser(userId) {
+ const url = buildApiUrl(USER_FOLLOW_PATH).replace(':id', encodeURIComponent(userId));
+ return axios.post(url);
+}
+
+export function unfollowUser(userId) {
+ const url = buildApiUrl(USER_UNFOLLOW_PATH).replace(':id', encodeURIComponent(userId));
+ return axios.post(url);
+}
+
+export function getCurrentUser(options) {
+ const url = buildApiUrl(CURRENT_USER_PATH);
+ return axios.get(url, { ...options });
+}
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index c3c28aeafc0..07fd6dae76a 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -43,7 +43,7 @@ function genericSuccess(e) {
}
/**
- * Safari > 10 doesn't support `execCommand`, so instead we inform the user to copy manually.
+ * Safari < 10 doesn't support `execCommand`, so instead we inform the user to copy manually.
* See http://clipboardjs.com/#browser-support
*/
function genericError(e) {
diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
index 6922ec9c5a5..3b9f6011c6d 100644
--- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
@@ -53,9 +53,6 @@ function fixElementSource(el) {
// Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
const source = el.textContent?.replace(/<br\s*\/>/g, '<br>');
- // Remove any extra spans added by the backend syntax highlighting.
- Object.assign(el, { textContent: source });
-
return { source };
}
@@ -78,17 +75,13 @@ function renderMermaidEl(el, source) {
width: '100%',
});
- // Add the original source into the DOM
- // to allow Copy-as-GFM to access it.
- const sourceEl = document.createElement('text');
- sourceEl.textContent = source;
- sourceEl.classList.add('gl-display-none');
-
const wrapper = document.createElement('div');
wrapper.appendChild(iframeEl);
- wrapper.appendChild(sourceEl);
- el.closest('pre').replaceWith(wrapper);
+ // Hide the markdown but keep it "visible enough" to allow Copy-as-GFM
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83202
+ el.closest('pre').classList.add('gl-sr-only');
+ el.closest('pre').parentNode.appendChild(wrapper);
// Event Listeners
iframeEl.addEventListener('load', () => {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 6124befd3b6..82229b5aa8f 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -156,7 +156,7 @@ export default class ShortcutsIssuable extends Shortcuts {
static copyBranchName() {
// There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed.
- const allCopyBtns = Array.from(document.querySelectorAll('.js-sidebar-source-branch button'));
+ const allCopyBtns = Array.from(document.querySelectorAll('.js-source-branch-copy'));
// Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated.
diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue
index 9832ebbea5c..f032e2e7fb8 100644
--- a/app/assets/javascripts/blob/components/blob_content.vue
+++ b/app/assets/javascripts/blob/components/blob_content.vue
@@ -66,7 +66,7 @@ export default {
</script>
<template>
<div class="blob-viewer" :data-type="activeViewer.type" :data-loaded="!loading">
- <gl-loading-icon v-if="loading" size="md" color="dark" class="my-4 mx-auto" />
+ <gl-loading-icon v-if="loading" size="lg" color="dark" class="my-4 mx-auto" />
<template v-else>
<blob-content-error
diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue
index 8a4fe1a9025..f78d921fa90 100644
--- a/app/assets/javascripts/blob/components/blob_header.vue
+++ b/app/assets/javascripts/blob/components/blob_header.vue
@@ -92,7 +92,7 @@ export default {
</blob-filepath>
</div>
- <div class="gl-sm-display-flex file-actions">
+ <div class="gl-display-flex gl-flex-wrap file-actions">
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" :doc-icon="blobSwitcherDocIcon" />
<slot name="actions"></slot>
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index e02217d0deb..4f970d657c2 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -1,10 +1,14 @@
import $ from 'jquery';
+import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
export default class FileTemplateSelector {
constructor(mediator) {
this.mediator = mediator;
this.$dropdown = null;
this.$wrapper = null;
+
+ this.dropdown = null;
+ this.wrapper = null;
}
init() {
@@ -12,18 +16,21 @@ export default class FileTemplateSelector {
this.$dropdown = $(cfg.dropdown);
this.$wrapper = $(cfg.wrapper);
- this.$dropdownIcon = this.$wrapper.find('.dropdown-menu-toggle-icon');
- this.$loadingIcon = $(
- '<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>',
- ).insertAfter(this.$dropdownIcon);
- this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text');
+
+ this.dropdown = document.querySelector(cfg.dropdown);
+ this.wrapper = document.querySelector(cfg.wrapper);
+
+ this.dropdownIcon = this.wrapper.querySelector('.dropdown-menu-toggle-icon');
+ this.loadingIcon = loadingIconForLegacyJS({ classes: ['gl-display-none'] });
+ this.dropdown.appendChild(this.loadingIcon);
+ this.dropdownToggleText = this.wrapper.querySelector('.dropdown-toggle-text');
this.initDropdown();
this.selectInitialTemplate();
}
selectInitialTemplate() {
- const template = this.$dropdown.data('selected');
+ const template = this.dropdown.dataset.selected;
if (!template) {
return;
@@ -33,11 +40,11 @@ export default class FileTemplateSelector {
}
show() {
- if (this.$dropdown === null) {
+ if (this.dropdown === null) {
this.init();
}
- this.$wrapper.removeClass('hidden');
+ this.wrapper.classList.remove('hidden');
/**
* We set the focus on the dropdown that was just shown. This is done so that, after selecting
@@ -49,36 +56,36 @@ export default class FileTemplateSelector {
* closed anymore.
*/
setTimeout(() => {
- this.$dropdown.focus();
+ this.dropdown.focus();
}, 0);
}
hide() {
- if (this.$dropdown !== null) {
- this.$wrapper.addClass('hidden');
+ if (this.dropdown !== null) {
+ this.wrapper.classList.add('hidden');
}
}
isHidden() {
- return !this.$wrapper || this.$wrapper.hasClass('hidden');
+ return !this.wrapper || this.wrapper.classList.contains('hidden');
}
getToggleText() {
- return this.$dropdownToggleText.text();
+ return this.dropdownToggleText.textContent;
}
setToggleText(text) {
- this.$dropdownToggleText.text(text);
+ this.dropdownToggleText.textContent = text;
}
renderLoading() {
- this.$loadingIcon.removeClass('gl-display-none');
- this.$dropdownIcon.addClass('gl-display-none');
+ this.loadingIcon.classList.remove('gl-display-none');
+ this.dropdownIcon.classList.add('gl-display-none');
}
renderLoaded() {
- this.$loadingIcon.addClass('gl-display-none');
- this.$dropdownIcon.removeClass('gl-display-none');
+ this.loadingIcon.classList.add('gl-display-none');
+ this.dropdownIcon.classList.remove('gl-display-none');
}
reportSelection(options) {
diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js
index a04da98ff77..4c497db9842 100644
--- a/app/assets/javascripts/blob/openapi/index.js
+++ b/app/assets/javascripts/blob/openapi/index.js
@@ -11,6 +11,7 @@ export default () => {
url: el.dataset.endpoint,
dom_id: '#js-openapi-viewer',
deepLinking: true,
+ displayOperationId: true,
});
})
.catch((error) => {
diff --git a/app/assets/javascripts/blob/pipeline_tour_success_modal.vue b/app/assets/javascripts/blob/pipeline_tour_success_modal.vue
index b4ca29114cb..f3c542c467a 100644
--- a/app/assets/javascripts/blob/pipeline_tour_success_modal.vue
+++ b/app/assets/javascripts/blob/pipeline_tour_success_modal.vue
@@ -132,7 +132,7 @@ export default {
<gl-button
ref="goToPipelines"
:href="goToPipelinesPath"
- variant="success"
+ variant="confirm"
:data-track-property="humanAccess"
:data-track-value="$options.goToTrackValuePipelines"
:data-track-action="$options.trackEvent"
diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js
index d257810da65..a92161bbc1b 100644
--- a/app/assets/javascripts/blob/sketch/index.js
+++ b/app/assets/javascripts/blob/sketch/index.js
@@ -27,7 +27,7 @@ export default class SketchLoader {
}
getZipFile() {
- return new JSZip.external.Promise((resolve, reject) => {
+ return new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(this.container.dataset.endpoint, (err, data) => {
if (err) {
reject(err);
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index ee2f6cfb46c..2ee2e199358 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import { SourceEditorExtension } from '~/editor/extensions/source_editor_extension_base';
import { FileTemplateExtension } from '~/editor/extensions/source_editor_file_template_ext';
+import { ToolbarExtension } from '~/editor/extensions/source_editor_toolbar_ext';
import SourceEditor from '~/editor/source_editor';
-import { getBlobLanguage } from '~/editor/utils';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
@@ -36,7 +36,7 @@ export default class EditBlob {
import('~/editor/extensions/source_editor_markdown_ext'),
import('~/editor/extensions/source_editor_markdown_livepreview_ext'),
]);
- this.editor.use([
+ this.markdownExtensions = this.editor.use([
{ definition: MarkdownExtension },
{
definition: MarkdownLivePreview,
@@ -48,7 +48,6 @@ export default class EditBlob {
message: `${BLOB_EDITOR_ERROR}: ${e}`,
});
}
- this.hasMarkdownExtension = true;
addEditorMarkdownListeners(this.editor);
}
@@ -58,8 +57,6 @@ export default class EditBlob {
const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form');
- this.hasMarkdownExtension = false;
-
const rootEditor = new SourceEditor();
this.editor = rootEditor.createInstance({
@@ -67,21 +64,29 @@ export default class EditBlob {
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
- this.editor.use([{ definition: SourceEditorExtension }, { definition: FileTemplateExtension }]);
+ this.editor.use([
+ { definition: SourceEditorExtension },
+ { definition: FileTemplateExtension },
+ { definition: ToolbarExtension },
+ ]);
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
- const newLang = getBlobLanguage(fileNameEl.value);
- if (newLang === 'markdown') {
- if (!this.hasMarkdownExtension) {
- this.fetchMarkdownExtension();
- }
- }
});
form.addEventListener('submit', () => {
fileContentEl.value = insertFinalNewline(this.editor.getValue());
});
+
+ // onDidChangeModelLanguage is part of the native Monaco API
+ // https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneCodeEditor.html#onDidChangeModelLanguage
+ this.editor.onDidChangeModelLanguage(({ newLanguage = '', oldLanguage = '' }) => {
+ if (newLanguage === 'markdown') {
+ this.fetchMarkdownExtension();
+ } else if (oldLanguage === 'markdown') {
+ this.editor.unuse(this.markdownExtensions);
+ }
+ });
}
initFileSelectors() {
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_form.vue b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
index 70ba90bb1d4..10c7a3db2d3 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column_form.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
@@ -80,17 +80,14 @@ export default {
<template>
<div
- class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
+ class="board-add-new-list board gl-display-inline-block gl-h-full gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0 gl-rounded-base gl-px-3"
data-testid="board-add-new-column"
data-qa-selector="board_add_new_list"
>
<div
- class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
+ class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
>
- <h3
- class="gl-font-size-h2 gl-px-5 gl-py-4 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
- data-testid="board-add-column-form-title"
- >
+ <h3 class="gl-font-size-h2 gl-px-5 gl-py-5 gl-m-0" data-testid="board-add-column-form-title">
{{ $options.i18n.newList }}
</h3>
@@ -98,7 +95,7 @@ export default {
class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-y-auto gl-align-items-flex-start"
>
<div class="gl-px-5">
- <h3 class="gl-font-lg gl-mt-5 gl-mb-2">
+ <h3 class="gl-font-lg gl-mt-3 gl-mb-2">
{{ $options.i18n.scope }}
</h3>
<p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
@@ -147,23 +144,18 @@ export default {
</gl-dropdown>
</gl-form-group>
</div>
- <div
- class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
- >
- <gl-button
- data-testid="cancelAddNewColumn"
- class="gl-ml-auto gl-mr-3"
- @click="setAddColumnFormVisibility(false)"
- >{{ $options.i18n.cancel }}</gl-button
- >
+ <div class="gl-display-flex gl-mb-4">
<gl-button
data-testid="addNewColumnButton"
:disabled="!selectedId"
variant="confirm"
- class="gl-mr-4"
+ class="gl-mr-3 gl-ml-4"
@click="$emit('add-list')"
>{{ $options.i18n.add }}</gl-button
>
+ <gl-button data-testid="cancelAddNewColumn" @click="setAddColumnFormVisibility(false)">{{
+ $options.i18n.cancel
+ }}</gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 814ff16efec..98ce1ac7f97 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -11,10 +11,12 @@ import { sortBy } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
+import { updateHistory } from '~/lib/utils/url_utility';
import { sprintf, __, n__ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { ListType } from '../constants';
+import eventHub from '../eventhub';
import BoardBlockedIcon from './board_blocked_icon.vue';
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
@@ -174,10 +176,19 @@ export default {
)
);
},
- labelTarget(label) {
+ filterByLabel(label) {
+ if (!this.updateFilters) return;
+
const filterPath = window.location.search ? `${window.location.search}&` : '?';
- const value = encodeURIComponent(label.title);
- return `${filterPath}label_name[]=${value}`;
+ const filter = `label_name[]=${encodeURIComponent(label.title)}`;
+
+ if (!filterPath.includes(filter)) {
+ updateHistory({
+ url: `${filterPath}${filter}`,
+ });
+ this.performSearch();
+ eventHub.$emit('updateTokens');
+ }
},
showScopedLabel(label) {
return this.scopedLabelsAvailable && isScopedLabel(label);
@@ -232,7 +243,7 @@ export default {
:description="label.description"
size="sm"
:scoped="showScopedLabel(label)"
- :target="labelTarget(label)"
+ @click="filterByLabel(label)"
/>
</template>
</div>
@@ -242,7 +253,7 @@ export default {
<div
class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden"
>
- <gl-loading-icon v-if="item.isLoading" size="md" class="mt-3" />
+ <gl-loading-icon v-if="item.isLoading" size="lg" class="mt-3" />
<span
v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 1d6a71aca47..8868b9b2f3e 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -75,7 +75,7 @@ export default {
v-if="!isSwimlanesOn"
ref="list"
v-bind="draggableOptions"
- class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
+ class="boards-list gl-w-full gl-py-5 gl-pr-3 gl-white-space-nowrap"
@end="moveList"
>
<board-column
@@ -85,10 +85,11 @@ export default {
:list="list"
:data-draggable-item-type="$options.draggableItemTypes.list"
:disabled="disabled"
+ :class="{ 'gl-xs-display-none!': addColumnFormVisible }"
/>
<transition name="slide" @after-enter="afterFormEnters">
- <board-add-new-column v-if="addColumnFormVisible" />
+ <board-add-new-column v-if="addColumnFormVisible" class="gl-xs-w-full!" />
</transition>
</component>
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
index aeb2cee590d..fa0c798ca9d 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -2,7 +2,8 @@
import { pickBy, isEmpty, mapValues } from 'lodash';
import { mapActions } from 'vuex';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
-import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import {
FILTERED_SEARCH_TERM,
@@ -10,6 +11,7 @@ import {
} from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { AssigneeFilterType } from '~/boards/constants';
+import eventHub from '../eventhub';
export default {
i18n: {
@@ -33,6 +35,7 @@ export default {
data() {
return {
filterParams: this.initialFilterParams,
+ filteredSearchKey: 0,
};
},
computed: {
@@ -306,12 +309,21 @@ export default {
},
},
created() {
+ eventHub.$on('updateTokens', this.updateTokens);
if (!isEmpty(this.eeFilters)) {
this.filterParams = this.eeFilters;
}
},
+ beforeDestroy() {
+ eventHub.$off('updateTokens', this.updateTokens);
+ },
methods: {
...mapActions(['performSearch']),
+ updateTokens() {
+ const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
+ this.filterParams = convertObjectPropsToCamelCase(rawFilterParams, {});
+ this.filteredSearchKey += 1;
+ },
handleFilter(filters) {
this.filterParams = this.getFilterParams(filters);
@@ -399,6 +411,7 @@ export default {
<template>
<filtered-search
+ :key="filteredSearchKey"
class="gl-w-full"
namespace=""
:tokens="tokens"
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index a874c9e070a..9d972860d06 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -17,6 +17,7 @@ const boardDefaults = {
labels: [],
milestone: {},
iterationCadence: {},
+ iterationCadenceId: null,
iteration: {},
assignee: {},
weight: null,
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 47f25f34d0c..66388f4eb43 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -287,7 +287,7 @@ export default {
:data-board-type="list.listType"
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
draggable=".board-card"
- class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2"
+ class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-3 gl-pt-0"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 9f70c84931f..a4298eb2544 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -126,7 +126,7 @@ export default {
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
},
chevronIcon() {
- return this.list.collapsed ? 'chevron-down' : 'chevron-right';
+ return this.list.collapsed ? 'chevron-right' : 'chevron-down';
},
isNewIssueShown() {
return (this.listType === ListType.backlog || this.showListHeaderButton) && !this.isEpicBoard;
@@ -248,7 +248,6 @@ export default {
<template>
<header
:class="{
- 'has-border': list.label && list.label.color,
'gl-h-full': list.collapsed,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
@@ -279,28 +278,6 @@ export default {
@click="toggleExpanded"
/>
<!-- EE start -->
- <span
- v-if="showMilestoneListDetails"
- aria-hidden="true"
- class="milestone-icon"
- :class="{
- 'gl-mt-3 gl-rotate-90': list.collapsed,
- 'gl-mr-2': !list.collapsed,
- }"
- >
- <gl-icon name="timer" />
- </span>
-
- <span
- v-if="showIterationListDetails"
- aria-hidden="true"
- :class="{
- 'gl-mt-3 gl-rotate-90': list.collapsed,
- 'gl-mr-2': !list.collapsed,
- }"
- >
- <gl-icon name="iteration" />
- </span>
<a
v-if="showAssigneeListDetails"
@@ -399,7 +376,7 @@ export default {
<span class="gl-display-inline-flex">
<gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" />
<span ref="itemCount" class="gl-display-inline-flex gl-align-items-center">
- <gl-icon class="gl-mr-2" :name="countIcon" />
+ <gl-icon class="gl-mr-2" :name="countIcon" :size="16" />
<item-count
v-if="!isLoading"
:items-size="isEpicBoard ? list.epicsCount : boardList.issuesCount"
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 24071c6f0b4..c559e4cdbd3 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -135,14 +135,14 @@ export default {
:modal-id="$options.modalId"
:title="$options.i18n.modalAction"
size="sm"
- :action-primary="{
+ :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: $options.i18n.modalAction,
attributes: [{ variant: 'danger' }],
- }"
- :action-secondary="{
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :action-secondary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: $options.i18n.modalCancel,
attributes: [{ variant: 'default' }],
- }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@primary="handleModalPrimary"
>
<p>{{ $options.i18n.modalCopy }}</p>
diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue
index f90ac1e9079..54a6e3000a4 100644
--- a/app/assets/javascripts/boards/components/board_top_bar.vue
+++ b/app/assets/javascripts/boards/components/board_top_bar.vue
@@ -33,7 +33,7 @@ export default {
class="issues-details-filters filtered-search-block gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row row-content-block second-block"
>
<div
- class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0! mb-md-2 mb-sm-0 gl-w-full"
+ class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0 gl-mb-3 gl-w-full"
>
<boards-selector />
<new-board-button />
@@ -41,7 +41,7 @@ export default {
<issue-board-filtered-search v-else />
</div>
<div
- class="filter-dropdown-container gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
+ class="filter-dropdown-container gl-md-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
>
<toggle-labels />
<toggle-epics-swimlanes v-if="swimlanesFeatureAvailable && isSignedIn" />
diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue
index 4746f598ab7..7002fd44294 100644
--- a/app/assets/javascripts/boards/components/config_toggle.vue
+++ b/app/assets/javascripts/boards/components/config_toggle.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
import { formType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import { s__, __ } from '~/locale';
@@ -14,8 +15,9 @@ export default {
GlModalDirective,
},
mixins: [Tracking.mixin()],
- inject: ['canAdminList', 'hasScope'],
+ inject: ['canAdminList'],
computed: {
+ ...mapGetters(['hasScope']),
buttonText() {
return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope');
},
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 1412411c275..247910301e7 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -91,6 +91,9 @@ export default {
loadMoreProjects() {
this.fetchGroupProjects({ search: this.searchTerm, fetchNext: true });
},
+ setFocus() {
+ this.$refs.search.focusInput();
+ },
},
};
</script>
@@ -107,8 +110,10 @@ export default {
block
menu-class="gl-w-full!"
:loading="initialLoading"
+ @shown="setFocus"
>
<gl-search-box-by-type
+ ref="search"
v-model.trim="searchTerm"
debounce="250"
:placeholder="$options.i18n.searchPlaceholder"
@@ -135,7 +140,7 @@ export default {
<span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
</gl-dropdown-text>
<gl-intersection-observer v-if="hasNextPage" @appear="loadMoreProjects">
- <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="md" />
+ <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="lg" />
</gl-intersection-observer>
</gl-dropdown>
</div>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
index 9d19fe57e7a..53e574e9942 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
@@ -130,7 +130,7 @@ export default {
@off-click="handleOffClick"
>
<template #title>
- <span class="gl-font-weight-bold" data-testid="item-title">{{ item.title }}</span>
+ <span data-testid="item-title">{{ item.title }}</span>
</template>
<template #collapsed>
<span class="gl-text-gray-800">{{ item.referencePath }}</span>
diff --git a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
index 81cc7b4d246..0e1d11727cf 100644
--- a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
@@ -2,8 +2,6 @@
mutation createBoardList($boardId: BoardID!, $backlog: Boolean, $labelId: LabelID) {
boardListCreate(input: { boardId: $boardId, backlog: $backlog, labelId: $labelId }) {
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
list {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_list_destroy.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_destroy.mutation.graphql
index ef3fd36e980..5cb1a74d5c7 100644
--- a/app/assets/javascripts/boards/graphql/board_list_destroy.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_destroy.mutation.graphql
@@ -1,4 +1,4 @@
-mutation DestroyBoardList($listId: ID!) {
+mutation DestroyBoardList($listId: ListID!) {
destroyBoardList(input: { listId: $listId }) {
errors
}
diff --git a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
index 7ea0e2f915a..13327028065 100644
--- a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
@@ -1,9 +1,7 @@
#import "./board_list.fragment.graphql"
-mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) {
+mutation UpdateBoardList($listId: ListID!, $position: Int, $collapsed: Boolean) {
updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) {
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
list {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_lists.query.graphql b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
index e6e98864aad..06e8c8783de 100644
--- a/app/assets/javascripts/boards/graphql/board_lists.query.graphql
+++ b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
@@ -2,7 +2,7 @@
query BoardLists(
$fullPath: ID!
- $boardId: ID!
+ $boardId: BoardID!
$filters: BoardIssueInput
$isGroup: Boolean = false
$isProject: Boolean = false
@@ -13,8 +13,6 @@ query BoardLists(
id
hideBacklogList
lists(issueFilters: $filters) {
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
nodes {
...BoardListFragment
}
@@ -27,8 +25,6 @@ query BoardLists(
id
hideBacklogList
lists(issueFilters: $filters) {
- # We have ID in a deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
nodes {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql
index bae3220dfad..f48383624c9 100644
--- a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql
+++ b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql
@@ -1,4 +1,4 @@
-query BoardList($id: ID!, $filters: BoardIssueInput) {
+query BoardList($id: ListID!, $filters: BoardIssueInput) {
boardList(id: $id, issueFilters: $filters) {
id
issuesCount
diff --git a/app/assets/javascripts/boards/graphql/group_board.query.graphql b/app/assets/javascripts/boards/graphql/group_board.query.graphql
index 8d87b83da96..526e3193efe 100644
--- a/app/assets/javascripts/boards/graphql/group_board.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_board.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/boards/graphql/board_scope.fragment.graphql"
-query GroupBoard($fullPath: ID!, $boardId: ID!) {
+query GroupBoard($fullPath: ID!, $boardId: BoardID!) {
workspace: group(fullPath: $fullPath) {
id
board(id: $boardId) {
diff --git a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
index 1658cf09085..89670760450 100644
--- a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
@@ -3,7 +3,7 @@
mutation issueMoveList(
$projectPath: ID!
$iid: String!
- $boardId: ID!
+ $boardId: BoardID!
$fromListId: ID
$toListId: ID
$moveBeforeId: ID
diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
index 994ea894be3..bf5329c4a8d 100644
--- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
+++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
@@ -2,8 +2,8 @@
query BoardListsEE(
$fullPath: ID!
- $boardId: ID!
- $id: ID
+ $boardId: BoardID!
+ $id: ListID
$filters: BoardIssueInput
$isGroup: Boolean = false
$isProject: Boolean = false
diff --git a/app/assets/javascripts/boards/graphql/project_board.query.graphql b/app/assets/javascripts/boards/graphql/project_board.query.graphql
index 8246d615a6a..2a9142696d2 100644
--- a/app/assets/javascripts/boards/graphql/project_board.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_board.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/boards/graphql/board_scope.fragment.graphql"
-query ProjectBoard($fullPath: ID!, $boardId: ID!) {
+query ProjectBoard($fullPath: ID!, $boardId: BoardID!) {
workspace: project(fullPath: $fullPath) {
id
board(id: $boardId) {
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 77c5994b5a1..8af7da1e0aa 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -69,7 +69,6 @@ function mountBoardApp(el) {
timeTrackingLimitToHours: parseBoolean(el.dataset.timeTrackingLimitToHours),
issuableType: issuableTypes.issue,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
- hasScope: parseBoolean(el.dataset.hasScope),
hasMissingBoards: parseBoolean(el.dataset.hasMissingBoards),
weights: el.dataset.weights ? JSON.parse(el.dataset.weights) : [],
// Permissions
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 82307da2572..a84b678a5d9 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -77,6 +77,7 @@ export default {
milestoneTitle: board.milestone?.title || null,
iterationId: board.iteration?.id || null,
iterationTitle: board.iteration?.title || null,
+ iterationCadenceId: board.iterationCadence?.id || null,
assigneeId: board.assignee?.id || null,
assigneeUsername: board.assignee?.username || null,
labels: board.labels?.nodes || [],
@@ -134,7 +135,7 @@ export default {
variables,
})
.then(({ data }) => {
- const { lists, hideBacklogList } = data[boardType]?.board;
+ const { lists, hideBacklogList } = data[boardType].board;
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists));
// Backlog list needs to be created if it doesn't exist and it's not hidden
if (!lists.nodes.find((l) => l.listType === ListType.backlog) && !hideBacklogList) {
@@ -429,7 +430,7 @@ export default {
variables,
})
.then(({ data }) => {
- const { lists } = data[boardType]?.board;
+ const { lists } = data[boardType].board;
const listItems = formatListIssues(lists);
const listPageInfo = formatListsPageInfo(lists);
commit(types.RECEIVE_ITEMS_FOR_LIST_SUCCESS, { listItems, listPageInfo, listId });
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index cb31eb4b008..e1891a4d954 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -51,4 +51,18 @@ export default {
isEpicBoard: () => {
return false;
},
+
+ hasScope: (state) => {
+ const { boardConfig } = state;
+ if (boardConfig.labels?.length > 0) {
+ return true;
+ }
+ let hasScope = false;
+ ['assigneeId', 'iterationCadenceId', 'iterationId', 'milestoneId', 'weight'].forEach((attr) => {
+ if (boardConfig[attr] !== null && boardConfig[attr] !== undefined) {
+ hasScope = true;
+ }
+ });
+ return hasScope;
+ },
};
diff --git a/app/assets/javascripts/branches/components/delete_branch_modal.vue b/app/assets/javascripts/branches/components/delete_branch_modal.vue
index 14c2badeb3f..383fa5f7512 100644
--- a/app/assets/javascripts/branches/components/delete_branch_modal.vue
+++ b/app/assets/javascripts/branches/components/delete_branch_modal.vue
@@ -32,17 +32,10 @@ export default {
return sprintf(modalTitle, { branchName: this.branchName });
},
- message() {
- const modalMessage = this.isProtectedBranch
+ modalMessage() {
+ return this.isProtectedBranch
? this.$options.i18n.modalMessageProtectedBranch
: this.$options.i18n.modalMessage;
-
- return sprintf(modalMessage, { branchName: this.branchName });
- },
- unmergedWarning() {
- return sprintf(this.$options.i18n.unmergedWarning, {
- defaultBranchName: this.defaultBranchName,
- });
},
undoneWarning() {
return sprintf(this.$options.i18n.undoneWarning, {
@@ -92,17 +85,15 @@ export default {
i18n: {
modalTitle: s__('Branches|Delete branch. Are you ABSOLUTELY SURE?'),
modalTitleProtectedBranch: s__('Branches|Delete protected branch. Are you ABSOLUTELY SURE?'),
- modalMessage: s__(
- "Branches|You're about to permanently delete the branch %{strongStart}%{branchName}.%{strongEnd}",
- ),
+ modalMessage: s__("Branches|You're about to permanently delete the branch %{branchName}."),
modalMessageProtectedBranch: s__(
- "Branches|You're about to permanently delete the protected branch %{strongStart}%{branchName}.%{strongEnd}",
+ "Branches|You're about to permanently delete the protected branch %{branchName}.",
),
unmergedWarning: s__(
- 'Branches|This branch hasn’t been merged into %{defaultBranchName}. To avoid data loss, consider merging this branch before deleting it.',
+ "Branches|This branch hasn't been merged into %{defaultBranchName}. To avoid data loss, consider merging this branch before deleting it.",
),
undoneWarning: s__(
- 'Branches|Once you confirm and press %{strongStart}%{buttonText},%{strongEnd} it cannot be undone or recovered.',
+ 'Branches|After you confirm and select %{strongStart}%{buttonText},%{strongEnd} you cannot recover this branch.',
),
cancelButtonText: s__('Branches|Cancel, keep branch'),
confirmationText: s__(
@@ -119,13 +110,19 @@ export default {
<gl-modal ref="modal" size="sm" :modal-id="modalId" :title="title">
<gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
<div data-testid="modal-message">
- <gl-sprintf :message="message">
- <template #strong="{ content }">
- <strong> {{ content }} </strong>
+ <gl-sprintf :message="modalMessage">
+ <template #branchName>
+ <strong>
+ <code class="gl-white-space-pre-wrap">{{ branchName }}</code>
+ </strong>
</template>
</gl-sprintf>
<p v-if="!merged" class="gl-mb-0 gl-mt-4">
- {{ unmergedWarning }}
+ <gl-sprintf :message="$options.i18n.unmergedWarning">
+ <template #defaultBranchName>
+ <code class="gl-white-space-pre-wrap">{{ defaultBranchName }}</code>
+ </template>
+ </gl-sprintf>
</p>
</div>
</gl-alert>
@@ -145,7 +142,7 @@ export default {
{{ content }}
</template>
</gl-sprintf>
- <code class="gl-white-space-pre-wrap"> {{ branchName }} </code>
+ <code class="gl-white-space-pre-wrap">{{ branchName }}</code>
<gl-form-input
v-model="enteredBranchName"
name="delete_branch_input"
diff --git a/app/assets/javascripts/captcha/captcha_modal.vue b/app/assets/javascripts/captcha/captcha_modal.vue
index b8b90b04beb..36aa098d5ff 100644
--- a/app/assets/javascripts/captcha/captcha_modal.vue
+++ b/app/assets/javascripts/captcha/captcha_modal.vue
@@ -107,7 +107,9 @@ export default {
ref="modal"
:modal-id="modalId"
:title="__('Please solve the captcha')"
- :action-cancel="{ text: __('Cancel') }"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: __('Cancel'),
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@shown="shown"
@hide="hide"
@hidden="$emit('hidden')"
diff --git a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
index d70ade36fe9..dbc4565b19d 100644
--- a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
+++ b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue
@@ -1,22 +1,48 @@
<script>
-import { GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlButton,
+ GlIcon,
+ GlLink,
+ GlLoadingIcon,
+ GlModal,
+ GlModalDirective,
+ GlPagination,
+ GlSprintf,
+ GlTable,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import Api, { DEFAULT_PER_PAGE } from '~/api';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { __ } from '~/locale';
+import httpStatusCodes from '~/lib/utils/http_status';
+import { __, s__, sprintf } from '~/locale';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
+ GlAlert,
+ GlButton,
+ GlIcon,
GlLink,
GlLoadingIcon,
+ GlModal,
GlPagination,
+ GlSprintf,
GlTable,
TimeagoTooltip,
},
- inject: ['projectId'],
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModal: GlModalDirective,
+ },
+ inject: ['projectId', 'admin', 'fileSizeLimit'],
docsLink: helpPagePath('ci/secure_files/index'),
DEFAULT_PER_PAGE,
i18n: {
+ deleteLabel: __('Delete File'),
+ uploadLabel: __('Upload File'),
+ uploadingLabel: __('Uploading...'),
pagination: {
next: __('Next'),
prev: __('Prev'),
@@ -26,27 +52,45 @@ export default {
'Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates.',
),
moreInformation: __('More information'),
+ uploadErrorMessages: {
+ duplicate: __('A file with this name already exists.'),
+ tooLarge: __('File too large. Secure Files must be less than %{limit} MB.'),
+ },
+ deleteModalTitle: s__('SecureFiles|Delete %{name}?'),
+ deleteModalMessage: s__(
+ 'SecureFiles|Secure File %{name} will be permanently deleted. Are you sure?',
+ ),
+ deleteModalButton: s__('SecureFiles|Delete secure file'),
},
+ deleteModalId: 'deleteModalId',
data() {
return {
page: 1,
totalItems: 0,
loading: false,
+ uploading: false,
+ error: false,
+ errorMessage: null,
projectSecureFiles: [],
+ deleteModalFileId: null,
+ deleteModalFileName: null,
};
},
fields: [
{
key: 'name',
label: __('Filename'),
- },
- {
- key: 'permissions',
- label: __('Permissions'),
+ tdClass: 'gl-vertical-align-middle!',
},
{
key: 'created_at',
label: __('Uploaded'),
+ tdClass: 'gl-vertical-align-middle!',
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'gl-text-right gl-vertical-align-middle!',
},
],
computed: {
@@ -63,6 +107,18 @@ export default {
this.getProjectSecureFiles();
},
methods: {
+ async deleteSecureFile(secureFileId) {
+ this.loading = true;
+ this.error = false;
+ try {
+ await Api.deleteProjectSecureFile(this.projectId, secureFileId);
+ this.getProjectSecureFiles();
+ } catch (error) {
+ Sentry.captureException(error);
+ this.error = true;
+ this.errorMessage = error;
+ }
+ },
async getProjectSecureFiles(page) {
this.loading = true;
const response = await Api.projectSecureFiles(this.projectId, { page });
@@ -72,6 +128,48 @@ export default {
this.projectSecureFiles = response.data;
this.loading = false;
+ this.uploading = false;
+ },
+ async uploadSecureFile() {
+ this.error = null;
+ this.uploading = true;
+ const [file] = this.$refs.fileUpload.files;
+ try {
+ await Api.uploadProjectSecureFile(this.projectId, this.uploadFormData(file));
+ this.getProjectSecureFiles();
+ } catch (error) {
+ this.error = true;
+ this.errorMessage = this.formattedErrorMessage(error);
+ this.uploading = false;
+ }
+ },
+ formattedErrorMessage(error) {
+ let message = '';
+ if (error?.response?.data?.message?.name) {
+ message = this.$options.i18n.uploadErrorMessages.duplicate;
+ } else if (error.response.status === httpStatusCodes.PAYLOAD_TOO_LARGE) {
+ message = sprintf(this.$options.i18n.uploadErrorMessages.tooLarge, {
+ limit: this.fileSizeLimit,
+ });
+ } else {
+ Sentry.captureException(error);
+ message = error;
+ }
+ return message;
+ },
+ loadFileSelctor() {
+ this.$refs.fileUpload.click();
+ },
+ setDeleteModalData(secureFile) {
+ this.deleteModalFileId = secureFile.id;
+ this.deleteModalFileName = secureFile.name;
+ },
+ uploadFormData(file) {
+ const formData = new FormData();
+ formData.append('name', file.name);
+ formData.append('file', file);
+
+ return formData;
},
},
};
@@ -79,16 +177,51 @@ export default {
<template>
<div>
- <h1 data-testid="title" class="gl-font-size-h1 gl-mt-3 gl-mb-0">{{ $options.i18n.title }}</h1>
+ <gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="error = null">
+ {{ errorMessage }}
+ </gl-alert>
+ <div class="row">
+ <div class="col-md-12 col-lg-6 gl-display-flex">
+ <div class="gl-flex-direction-column gl-flex-wrap">
+ <h1 class="gl-font-size-h1 gl-mt-3 gl-mb-0">
+ {{ $options.i18n.title }}
+ </h1>
+ </div>
+ </div>
+
+ <div class="col-md-12 col-lg-6">
+ <div class="gl-display-flex gl-flex-wrap gl-justify-content-end">
+ <gl-button v-if="admin" class="gl-mt-3" variant="info" @click="loadFileSelctor">
+ <span v-if="uploading">
+ <gl-loading-icon size="sm" class="gl-my-5" inline />
+ {{ $options.i18n.uploadingLabel }}
+ </span>
+ <span v-else>
+ <gl-icon name="upload" class="gl-mr-2" /> {{ $options.i18n.uploadLabel }}
+ </span>
+ </gl-button>
+ <input
+ id="file-upload"
+ ref="fileUpload"
+ type="file"
+ class="hidden"
+ data-qa-selector="file_upload_field"
+ @change="uploadSecureFile"
+ />
+ </div>
+ </div>
+ </div>
- <p>
- <span data-testid="info-message" class="gl-mr-2">
- {{ $options.i18n.overviewMessage }}
- <gl-link :href="$options.docsLink" target="_blank">{{
- $options.i18n.moreInformation
- }}</gl-link>
- </span>
- </p>
+ <div class="row">
+ <div class="col-md-12 col-lg-12 gl-my-4">
+ <span data-testid="info-message">
+ {{ $options.i18n.overviewMessage }}
+ <gl-link :href="$options.docsLink" target="_blank">{{
+ $options.i18n.moreInformation
+ }}</gl-link>
+ </span>
+ </div>
+ </div>
<gl-table
:busy="loading"
@@ -112,14 +245,23 @@ export default {
{{ item.name }}
</template>
- <template #cell(permissions)="{ item }">
- {{ item.permissions }}
- </template>
-
<template #cell(created_at)="{ item }">
<timeago-tooltip :time="item.created_at" />
</template>
+
+ <template #cell(actions)="{ item }">
+ <gl-button
+ v-if="admin"
+ v-gl-modal="$options.deleteModalId"
+ v-gl-tooltip.hover.top="$options.i18n.deleteLabel"
+ variant="danger"
+ icon="remove"
+ :aria-label="$options.i18n.deleteLabel"
+ @click="setDeleteModalData(item)"
+ />
+ </template>
</gl-table>
+
<gl-pagination
v-if="!loading"
v-model="page"
@@ -129,5 +271,25 @@ export default {
:prev-text="$options.i18n.pagination.prev"
align="center"
/>
+
+ <gl-modal
+ :ref="$options.deleteModalId"
+ :modal-id="$options.deleteModalId"
+ title-tag="h4"
+ category="primary"
+ :ok-title="$options.i18n.deleteModalButton"
+ ok-variant="danger"
+ @ok="deleteSecureFile(deleteModalFileId)"
+ >
+ <template #modal-title>
+ <gl-sprintf :message="$options.i18n.deleteModalTitle">
+ <template #name>{{ deleteModalFileName }}</template>
+ </gl-sprintf>
+ </template>
+
+ <gl-sprintf :message="$options.i18n.deleteModalMessage">
+ <template #name>{{ deleteModalFileName }}</template>
+ </gl-sprintf>
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/ci_secure_files/index.js b/app/assets/javascripts/ci_secure_files/index.js
index 18b4ac6866e..3944286dc60 100644
--- a/app/assets/javascripts/ci_secure_files/index.js
+++ b/app/assets/javascripts/ci_secure_files/index.js
@@ -1,14 +1,19 @@
import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import SecureFilesList from './components/secure_files_list.vue';
export const initCiSecureFiles = (selector = '#js-ci-secure-files') => {
const containerEl = document.querySelector(selector);
const { projectId } = containerEl.dataset;
+ const { admin } = containerEl.dataset;
+ const { fileSizeLimit } = containerEl.dataset;
return new Vue({
el: containerEl,
provide: {
projectId,
+ admin: parseBoolean(admin),
+ fileSizeLimit,
},
render(createElement) {
return createElement(SecureFilesList);
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
index be2366108b3..3af89dc4a2c 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
@@ -26,6 +26,7 @@ import {
AWS_TIP_DISMISSED_COOKIE_NAME,
AWS_TIP_MESSAGE,
CONTAINS_VARIABLE_REFERENCE_MESSAGE,
+ ENVIRONMENT_SCOPE_LINK_TITLE,
EVENT_LABEL,
EVENT_ACTION,
} from '../constants';
@@ -40,6 +41,7 @@ export default {
tokenList: awsTokenList,
awsTipMessage: AWS_TIP_MESSAGE,
containsVariableReferenceMessage: CONTAINS_VARIABLE_REFERENCE_MESSAGE,
+ environmentScopeLinkTitle: ENVIRONMENT_SCOPE_LINK_TITLE,
components: {
CiEnvironmentsDropdown,
GlAlert,
@@ -81,6 +83,7 @@ export default {
'containsVariableReferenceLink',
'protectedEnvironmentVariablesLink',
'maskedEnvironmentVariablesLink',
+ 'environmentScopeLink',
]),
...mapComputed(
[
@@ -109,7 +112,7 @@ export default {
return regex.test(this.variable.secret_value);
},
containsVariableReference() {
- const regex = RegExp(/\$/);
+ const regex = /\$/;
return regex.test(this.variable.secret_value);
},
displayMaskedError() {
@@ -278,12 +281,18 @@ export default {
<gl-form-select id="ci-variable-type" v-model="variable_type" :options="typeOptions" />
</gl-form-group>
- <gl-form-group
- :label="__('Environment scope')"
- label-for="ci-variable-env"
- class="w-50"
- data-testid="environment-scope"
- >
+ <gl-form-group label-for="ci-variable-env" class="w-50" data-testid="environment-scope">
+ <template #label>
+ {{ __('Environment scope') }}
+ <gl-link
+ :title="$options.environmentScopeLinkTitle"
+ :href="environmentScopeLink"
+ target="_blank"
+ data-testid="environment-scope-link"
+ >
+ <gl-icon name="question" :size="12" />
+ </gl-link>
+ </template>
<ci-environments-dropdown
v-if="scopedVariablesAvailable"
class="w-100"
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
index 61636b389da..f078234829a 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
@@ -179,10 +179,7 @@ export default {
</p>
</template>
</gl-table>
- <div
- class="ci-variable-actions gl-display-flex"
- :class="{ 'gl-justify-content-center': isTableEmpty }"
- >
+ <div class="ci-variable-actions gl-display-flex gl-mt-5">
<gl-button
v-gl-modal-directive="$options.modalId"
class="gl-mr-3"
diff --git a/app/assets/javascripts/ci_variable_list/constants.js b/app/assets/javascripts/ci_variable_list/constants.js
index 663a912883b..fa55b4d9e77 100644
--- a/app/assets/javascripts/ci_variable_list/constants.js
+++ b/app/assets/javascripts/ci_variable_list/constants.js
@@ -31,3 +31,5 @@ export const AWS_TOKEN_CONSTANTS = [AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, AWS_S
export const CONTAINS_VARIABLE_REFERENCE_MESSAGE = __(
'Values that contain the %{codeStart}$%{codeEnd} character can be considered a variable reference and expanded. %{docsLinkStart}Learn more.%{docsLinkEnd}',
);
+
+export const ENVIRONMENT_SCOPE_LINK_TITLE = __('Learn more');
diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js
index 7c40f8134d4..f771751194c 100644
--- a/app/assets/javascripts/ci_variable_list/index.js
+++ b/app/assets/javascripts/ci_variable_list/index.js
@@ -17,6 +17,7 @@ const mountCiVariableListApp = (containerEl) => {
containsVariableReferenceLink,
protectedEnvironmentVariablesLink,
maskedEnvironmentVariablesLink,
+ environmentScopeLink,
} = containerEl.dataset;
const isGroup = parseBoolean(group);
const isProtectedByDefault = parseBoolean(protectedByDefault);
@@ -34,6 +35,7 @@ const mountCiVariableListApp = (containerEl) => {
containsVariableReferenceLink,
protectedEnvironmentVariablesLink,
maskedEnvironmentVariablesLink,
+ environmentScopeLink,
});
return new Vue({
diff --git a/app/assets/javascripts/clone_panel.js b/app/assets/javascripts/clone_panel.js
index ec831a77bde..79280c13f0f 100644
--- a/app/assets/javascripts/clone_panel.js
+++ b/app/assets/javascripts/clone_panel.js
@@ -17,7 +17,12 @@ export default function initClonePanel() {
$('a', $cloneOptions).on('click', (e) => {
const $this = $(e.currentTarget);
const url = $this.attr('href');
- if (url && (url.startsWith('vscode://') || url.startsWith('xcode://'))) {
+ if (
+ url &&
+ (url.startsWith('vscode://') ||
+ url.startsWith('xcode://') ||
+ url.startsWith('jetbrains://'))
+ ) {
// Clone with "..." should open like a normal link
return;
}
diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
index 6567ce203bc..18c6503bfb2 100644
--- a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
+++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
@@ -28,17 +28,17 @@ export default {
},
i18n: {
emptyText: s__(
- 'ClusterAgents|See Agent activity updates such as tokens created or revoked and clusters connected or not connected.',
+ 'ClusterAgents|See agent activity updates, like tokens created or revoked and clusters connected or not connected.',
),
- emptyTooltip: s__('ClusterAgents|What is GitLab Agent activity?'),
+ emptyTooltip: s__('ClusterAgents|What is agent activity?'),
error: s__(
- 'ClusterAgents|An error occurred while retrieving GitLab Agent activity. Reload the page to try again.',
+ 'ClusterAgents|An error occurred while retrieving agent activity. Reload the page to try again.',
),
today: __('Today'),
yesterday: __('Yesterday'),
},
- emptyHelpLink: helpPagePath('user/clusters/agent/install/index', {
- anchor: 'view-agent-activity',
+ emptyHelpLink: helpPagePath('user/clusters/agent/work_with_agent', {
+ anchor: 'view-an-agents-activity-information',
}),
borderClasses: 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100',
apollo: {
@@ -68,8 +68,8 @@ export default {
},
emptyStateTitle() {
return n__(
- "ClusterAgents|There's no activity from the past day",
- "ClusterAgents|There's no activity from the past %d days",
+ 'ClusterAgents|No activity occurred in the past day',
+ 'ClusterAgents|No activity occurred in the past %d days',
EVENTS_STORED_DAYS,
);
},
@@ -124,7 +124,7 @@ export default {
<template>
<div>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<div v-else-if="hasEvents">
<div
diff --git a/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue b/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue
new file mode 100644
index 00000000000..7d36cbb170d
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/components/revoke_token_button.vue
@@ -0,0 +1,201 @@
+<script>
+import {
+ GlButton,
+ GlModalDirective,
+ GlTooltip,
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlSprintf,
+} from '@gitlab/ui';
+import { s__, __, sprintf } from '~/locale';
+import { REVOKE_TOKEN_MODAL_ID, TOKEN_STATUS_ACTIVE } from '../constants';
+import revokeAgentToken from '../graphql/mutations/revoke_token.mutation.graphql';
+import getClusterAgentQuery from '../graphql/queries/get_cluster_agent.query.graphql';
+import { removeTokenFromStore } from '../graphql/cache_update';
+
+export default {
+ components: {
+ GlButton,
+ GlTooltip,
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlSprintf,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['agentName', 'projectPath', 'canAdminCluster'],
+ props: {
+ token: {
+ required: true,
+ type: Object,
+ validator: (value) => ['id', 'name'].every((prop) => value[prop]),
+ },
+ cursor: {
+ required: true,
+ type: Object,
+ },
+ },
+ i18n: {
+ revokeButton: s__('ClusterAgents|Revoke token'),
+ dropdownDisabledHint: s__(
+ 'ClusterAgents|Requires a Maintainer or greater role to perform this action',
+ ),
+ modalTitle: s__('ClusterAgents|Revoke access token?'),
+ modalBody: s__(
+ 'ClusterAgents|Are you sure you want to revoke this token? You cannot undo this action.',
+ ),
+ modalInputLabel: s__('ClusterAgents|To revoke the token, type %{name} to confirm:'),
+ modalCancel: __('Cancel'),
+ successMessage: s__('ClusterAgents|%{name} successfully revoked'),
+ defaultError: __('An error occurred. Please try again.'),
+ },
+ data() {
+ return {
+ loading: false,
+ error: null,
+ revokeConfirmText: null,
+ tokenName: null,
+ variables: {
+ agentName: this.agentName,
+ projectPath: this.projectPath,
+ tokenStatus: TOKEN_STATUS_ACTIVE,
+ ...this.cursor,
+ },
+ };
+ },
+ computed: {
+ revokeBtnDisabled() {
+ return this.loading || !this.canAdminCluster;
+ },
+ modalId() {
+ return sprintf(REVOKE_TOKEN_MODAL_ID, {
+ tokenName: this.token.name,
+ });
+ },
+ primaryModalProps() {
+ return {
+ text: this.$options.i18n.revokeButton,
+ attributes: [
+ { disabled: this.loading || this.disableModalSubmit, loading: this.loading },
+ { variant: 'danger' },
+ ],
+ };
+ },
+ cancelModalProps() {
+ return {
+ text: this.$options.i18n.modalCancel,
+ attributes: [],
+ };
+ },
+ disableModalSubmit() {
+ return this.revokeConfirmText !== this.token.name;
+ },
+ },
+ methods: {
+ async revokeToken() {
+ if (this.disableModalSubmit || this.loading) {
+ return;
+ }
+
+ this.loading = true;
+ this.error = null;
+ this.tokenName = this.token.name;
+
+ try {
+ const { errors } = await this.revokeTokenMutation();
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ this.error = error?.message || this.$options.i18n.defaultError;
+ } finally {
+ this.loading = false;
+ const successMessage = sprintf(this.$options.i18n.successMessage, {
+ name: this.tokenName,
+ });
+
+ this.$toast.show(this.error || successMessage);
+
+ this.hideModal();
+ }
+ },
+ revokeTokenMutation() {
+ return this.$apollo
+ .mutate({
+ mutation: revokeAgentToken,
+ variables: {
+ input: {
+ id: this.token.id,
+ },
+ },
+ update: (store) => {
+ removeTokenFromStore(store, this.token, getClusterAgentQuery, this.variables);
+ },
+ })
+
+ .then(({ data: { clusterAgentTokenRevoke } }) => {
+ return clusterAgentTokenRevoke;
+ });
+ },
+ resetModal() {
+ this.loading = false;
+ this.error = null;
+ this.revokeConfirmText = null;
+ },
+ hideModal() {
+ this.resetModal();
+ this.$refs.modal.hide();
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div ref="revokeToken" class="gl-display-inline-block">
+ <gl-button
+ v-gl-modal-directive="modalId"
+ icon="remove"
+ category="secondary"
+ variant="danger"
+ :disabled="revokeBtnDisabled"
+ :title="$options.i18n.revokeButton"
+ :aria-label="$options.i18n.revokeButton"
+ />
+
+ <gl-tooltip
+ v-if="!canAdminCluster"
+ :target="() => $refs.revokeToken"
+ :title="$options.i18n.dropdownDisabledHint"
+ />
+ </div>
+
+ <gl-modal
+ ref="modal"
+ :modal-id="modalId"
+ :title="$options.i18n.modalTitle"
+ :action-primary="primaryModalProps"
+ :action-cancel="cancelModalProps"
+ size="sm"
+ @primary="revokeToken"
+ @hide="hideModal"
+ >
+ <p>{{ $options.i18n.modalBody }}</p>
+
+ <gl-form-group>
+ <template #label>
+ <gl-sprintf :message="$options.i18n.modalInputLabel">
+ <template #name>
+ <code>{{ token.name }}</code>
+ </template>
+ </gl-sprintf>
+ </template>
+ <gl-form-input v-model="revokeConfirmText" @keydown.enter="revokeToken" />
+ </gl-form-group>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index 5df3e0811a5..e3de8339325 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -140,7 +140,7 @@ export default {
</span>
</template>
- <gl-loading-icon v-if="isLoading" size="md" class="gl-m-3" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-m-3" />
<div v-else>
<token-table :tokens="tokens" :cluster-agent-id="clusterAgent.id" :cursor="cursor" />
diff --git a/app/assets/javascripts/clusters/agents/components/token_table.vue b/app/assets/javascripts/clusters/agents/components/token_table.vue
index fbb39c28d78..9e64c9da712 100644
--- a/app/assets/javascripts/clusters/agents/components/token_table.vue
+++ b/app/assets/javascripts/clusters/agents/components/token_table.vue
@@ -3,6 +3,7 @@ import { GlEmptyState, GlTable, GlTooltip, GlTruncate } from '@gitlab/ui';
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CreateTokenButton from './create_token_button.vue';
+import RevokeTokenButton from './revoke_token_button.vue';
export default {
components: {
@@ -12,6 +13,7 @@ export default {
GlTruncate,
TimeAgoTooltip,
CreateTokenButton,
+ RevokeTokenButton,
},
i18n: {
createdBy: s__('ClusterAgents|Created by'),
@@ -66,6 +68,11 @@ export default {
label: this.$options.i18n.description,
tdAttr: { 'data-testid': 'agent-token-description' },
},
+ {
+ key: 'actions',
+ label: '',
+ tdAttr: { 'data-testid': 'agent-token-revoke' },
+ },
];
},
},
@@ -119,6 +126,10 @@ export default {
</gl-tooltip>
</div>
</template>
+
+ <template #cell(actions)="{ item }">
+ <revoke-token-button :token="item" :cluster-agent-id="clusterAgentId" :cursor="cursor" />
+ </template>
</gl-table>
</div>
diff --git a/app/assets/javascripts/clusters/agents/constants.js b/app/assets/javascripts/clusters/agents/constants.js
index 50d8f5e9e40..962fa243903 100644
--- a/app/assets/javascripts/clusters/agents/constants.js
+++ b/app/assets/javascripts/clusters/agents/constants.js
@@ -44,3 +44,5 @@ export const EVENT_ACTIONS_OPEN = 'open_modal';
export const EVENT_ACTIONS_CLICK = 'click_button';
export const TOKEN_NAME_LIMIT = 255;
+
+export const REVOKE_TOKEN_MODAL_ID = 'revoke-token-%{tokenName}';
diff --git a/app/assets/javascripts/clusters/agents/graphql/cache_update.js b/app/assets/javascripts/clusters/agents/graphql/cache_update.js
index 0219c4150eb..8db79c82708 100644
--- a/app/assets/javascripts/clusters/agents/graphql/cache_update.js
+++ b/app/assets/javascripts/clusters/agents/graphql/cache_update.js
@@ -22,3 +22,25 @@ export function addAgentTokenToStore(store, clusterAgentTokenCreate, query, vari
});
}
}
+
+export function removeTokenFromStore(store, revokeToken, query, variables) {
+ if (!hasErrors(revokeToken)) {
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ draftData.project.clusterAgent.tokens.nodes = draftData.project.clusterAgent.tokens.nodes.filter(
+ ({ id }) => id !== revokeToken.id,
+ );
+ draftData.project.clusterAgent.tokens.count -= 1;
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
+ }
+}
diff --git a/app/assets/javascripts/clusters/agents/graphql/mutations/revoke_token.mutation.graphql b/app/assets/javascripts/clusters/agents/graphql/mutations/revoke_token.mutation.graphql
new file mode 100644
index 00000000000..6f1c6a66690
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/graphql/mutations/revoke_token.mutation.graphql
@@ -0,0 +1,5 @@
+mutation revokeAgentToken($input: ClusterAgentTokenRevokeInput!) {
+ clusterAgentTokenRevoke(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/clusters/components/new_cluster.vue b/app/assets/javascripts/clusters/components/new_cluster.vue
index 8f3e2916270..41a33a8459f 100644
--- a/app/assets/javascripts/clusters/components/new_cluster.vue
+++ b/app/assets/javascripts/clusters/components/new_cluster.vue
@@ -1,6 +1,6 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapState } from 'vuex';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
export default {
@@ -10,13 +10,11 @@ export default {
'ClusterIntegration|Enter details about your cluster. %{linkStart}How do I use a certificate to connect to my cluster?%{linkEnd}',
),
},
+ clusterConnectHelpPath: helpPagePath('user/project/clusters/add_existing_cluster'),
components: {
GlLink,
GlSprintf,
},
- computed: {
- ...mapState(['clusterConnectHelpPath']),
- },
};
</script>
@@ -26,7 +24,7 @@ export default {
<p>
<gl-sprintf :message="$options.i18n.information">
<template #link="{ content }">
- <gl-link :href="clusterConnectHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.clusterConnectHelpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
index 98db620e3ab..dca89133931 100644
--- a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
+++ b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
@@ -159,7 +159,7 @@ export default {
)
}}</span>
<template #modal-footer>
- <gl-button variant="secondary" @click="handleCancel">{{ __('Cancel') }}</gl-button>
+ <gl-button @click="handleCancel">{{ __('Cancel') }}</gl-button>
<template v-if="confirmCleanup">
<gl-button
:disabled="!canSubmit"
diff --git a/app/assets/javascripts/clusters/forms/components/integration_form.vue b/app/assets/javascripts/clusters/forms/components/integration_form.vue
index 3f61a1b18a7..b2a8381f937 100644
--- a/app/assets/javascripts/clusters/forms/components/integration_form.vue
+++ b/app/assets/javascripts/clusters/forms/components/integration_form.vue
@@ -140,7 +140,7 @@ export default {
<div v-if="editable" class="form group gl-display-flex gl-justify-content-end">
<gl-button
category="primary"
- variant="success"
+ variant="confirm"
type="submit"
:disabled="!canSubmit"
:aria-disabled="!canSubmit"
diff --git a/app/assets/javascripts/create_cluster/gke_cluster_namespace/index.js b/app/assets/javascripts/clusters/gke_cluster_namespace/index.js
index 2b3dfb99328..2b3dfb99328 100644
--- a/app/assets/javascripts/create_cluster/gke_cluster_namespace/index.js
+++ b/app/assets/javascripts/clusters/gke_cluster_namespace/index.js
diff --git a/app/assets/javascripts/clusters/new_cluster.js b/app/assets/javascripts/clusters/new_cluster.js
index 71f585fd307..4df6872bcc1 100644
--- a/app/assets/javascripts/clusters/new_cluster.js
+++ b/app/assets/javascripts/clusters/new_cluster.js
@@ -1,17 +1,15 @@
import Vue from 'vue';
import NewCluster from './components/new_cluster.vue';
-import { createStore } from './stores/new_cluster';
export default () => {
- const entryPoint = document.querySelector('#js-cluster-new');
+ const el = document.querySelector('#js-cluster-new');
- if (!entryPoint) {
+ if (!el) {
return null;
}
return new Vue({
- el: '#js-cluster-new',
- store: createStore(entryPoint.dataset),
+ el,
render(createElement) {
return createElement(NewCluster);
},
diff --git a/app/assets/javascripts/clusters/stores/new_cluster/index.js b/app/assets/javascripts/clusters/stores/new_cluster/index.js
deleted file mode 100644
index 87f1c05fdf9..00000000000
--- a/app/assets/javascripts/clusters/stores/new_cluster/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import state from './state';
-
-Vue.use(Vuex);
-
-export const createStore = (initialState) =>
- new Vuex.Store({
- state: state(initialState),
- });
-
-export default createStore;
diff --git a/app/assets/javascripts/clusters/stores/new_cluster/state.js b/app/assets/javascripts/clusters/stores/new_cluster/state.js
deleted file mode 100644
index 1ca1ac8de18..00000000000
--- a/app/assets/javascripts/clusters/stores/new_cluster/state.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default (initialState = {}) => ({
- clusterConnectHelpPath: initialState.clusterConnectHelpPath,
-});
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 2decdb5307b..496baf8cb08 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -39,7 +39,7 @@ export default {
configHelpLink: helpPagePath('user/clusters/agent/install/index', {
anchor: 'create-an-agent-configuration-file',
}),
- inject: ['gitlabVersion'],
+ inject: ['gitlabVersion', 'kasVersion'],
props: {
agents: {
required: true,
@@ -102,6 +102,9 @@ export default {
return { ...agent, versions };
});
},
+ serverVersion() {
+ return this.kasVersion || this.gitlabVersion;
+ },
},
methods: {
getStatusCellId(item) {
@@ -135,12 +138,12 @@ export default {
if (!agent.versions.length) return false;
const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.');
- const [gitlabMajorVersion, gitlabMinorVersion] = this.gitlabVersion.split('.');
+ const [serverMajorVersion, serverMinorVersion] = this.serverVersion.split('.');
- const majorVersionMismatch = agentMajorVersion !== gitlabMajorVersion;
+ const majorVersionMismatch = agentMajorVersion !== serverMajorVersion;
// We should warn user if their current GitLab and agent versions are more than 1 minor version apart:
- const minorVersionMismatch = Math.abs(agentMinorVersion - gitlabMinorVersion) > 1;
+ const minorVersionMismatch = Math.abs(agentMinorVersion - serverMinorVersion) > 1;
return majorVersionMismatch || minorVersionMismatch;
},
@@ -165,8 +168,6 @@ export default {
:items="agentsList"
:fields="fields"
stacked="md"
- head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
class="gl-mb-4!"
data-testid="cluster-agent-list-table"
>
@@ -242,7 +243,7 @@ export default {
<p class="gl-mb-0">
<gl-sprintf :message="$options.i18n.versionOutdatedText">
- <template #version>{{ gitlabVersion }}</template>
+ <template #version>{{ serverVersion }}</template>
</gl-sprintf>
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
{{ $options.i18n.viewDocsText }}</gl-link
@@ -255,7 +256,7 @@ export default {
<p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
<gl-sprintf :message="$options.i18n.versionOutdatedText">
- <template #version>{{ gitlabVersion }}</template>
+ <template #version>{{ serverVersion }}</template>
</gl-sprintf>
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
{{ $options.i18n.viewDocsText }}</gl-link
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 89b18ed6d06..8a4a81d3e96 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -175,7 +175,7 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<section v-else-if="agentList">
<div v-if="agentList.length">
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index 59cfdde731d..fb3c8ff66b0 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -224,7 +224,7 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loadingClusters" size="md" />
+ <gl-loading-icon v-if="loadingClusters" size="lg" />
<section v-else>
<ancestor-notice />
@@ -235,8 +235,6 @@ export default {
:fields="fields"
fixed
stacked="md"
- head-variant="white"
- thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
class="qa-clusters-table gl-mb-4!"
data-testid="cluster_list_table"
>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
index 8fd759bd3e9..2675d46dd16 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_actions.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
@@ -1,5 +1,5 @@
<script>
-import { GlDropdown, GlDropdownItem, GlModalDirective, GlTooltip } from '@gitlab/ui';
+import { GlButton, GlDropdown, GlDropdownItem, GlModalDirective, GlTooltip } from '@gitlab/ui';
import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '../constants';
@@ -7,6 +7,7 @@ export default {
i18n: CLUSTERS_ACTIONS,
INSTALL_AGENT_MODAL_ID,
components: {
+ GlButton,
GlDropdown,
GlDropdownItem,
GlTooltip,
@@ -15,7 +16,6 @@ export default {
GlModalDirective,
},
inject: [
- 'newClusterPath',
'addClusterPath',
'newClusterDocsPath',
'canAddCluster',
@@ -42,20 +42,59 @@ export default {
}
return this.addClusterPath;
},
+ actionItems() {
+ const createCluster = {
+ href: this.newClusterDocsPath,
+ title: this.$options.i18n.createCluster,
+ testid: 'create-cluster-link',
+ };
+ const connectCluster = {
+ href: this.addClusterPath,
+ title: this.$options.i18n.connectClusterCertificate,
+ testid: 'connect-cluster-link',
+ };
+ const actions = [];
+
+ if (this.displayClusterAgents) {
+ actions.push(createCluster);
+ }
+ if (this.displayClusterAgents && this.certificateBasedClustersEnabled) {
+ actions.push(connectCluster);
+ }
+
+ return actions;
+ },
+ },
+ methods: {
+ getTooltipTarget() {
+ return this.actionItems.length ? this.$refs.actions.$el : this.$refs.actionsContainer;
+ },
},
};
</script>
<template>
- <div class="nav-controls gl-ml-auto">
+ <div ref="actionsContainer" class="nav-controls gl-ml-auto">
<gl-tooltip
v-if="!canAddCluster"
- :target="() => $refs.dropdown.$el"
- :title="$options.i18n.dropdownDisabledHint"
+ :target="() => getTooltipTarget()"
+ :title="$options.i18n.actionsDisabledHint"
/>
+ <gl-button
+ v-if="!actionItems.length"
+ data-qa-selector="clusters_actions_button"
+ category="primary"
+ variant="confirm"
+ :disabled="!canAddCluster"
+ :href="defaultActionUrl"
+ >
+ {{ defaultActionText }}
+ </gl-button>
+
<gl-dropdown
- ref="dropdown"
+ v-else
+ ref="actions"
v-gl-modal-directive="shouldTriggerModal && $options.INSTALL_AGENT_MODAL_ID"
data-qa-selector="clusters_actions_button"
category="primary"
@@ -67,31 +106,13 @@ export default {
right
>
<gl-dropdown-item
- v-if="displayClusterAgents"
- :href="newClusterDocsPath"
- data-testid="create-cluster-link"
- @click.stop
- >
- {{ $options.i18n.createCluster }}
- </gl-dropdown-item>
-
- <template v-if="displayClusterAgents && certificateBasedClustersEnabled">
- <gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
- {{ $options.i18n.createClusterCertificate }}
- </gl-dropdown-item>
-
- <gl-dropdown-item :href="addClusterPath" data-testid="connect-cluster-link" @click.stop>
- {{ $options.i18n.connectClusterCertificate }}
- </gl-dropdown-item>
- </template>
-
- <gl-dropdown-item
- v-if="certificateBasedClustersEnabled && !displayClusterAgents"
- :href="newClusterPath"
- data-testid="new-cluster-link"
+ v-for="action in actionItems"
+ :key="action.title"
+ :href="action.href"
+ :data-testid="action.testid"
@click.stop
>
- {{ $options.i18n.createClusterDeprecated }}
+ {{ action.title }}
</gl-dropdown-item>
</gl-dropdown>
</div>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
index 73ca804e111..d831d79b994 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue
@@ -89,7 +89,7 @@ export default {
</script>
<template>
<div>
- <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-loading-icon v-if="isLoading" size="lg" />
<div v-show="!isLoading" data-testid="clusters-cards-container">
<gl-card
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between gl-py-4"
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index 4a168e811aa..10e71513065 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -234,11 +234,9 @@ export const CLUSTERS_ACTIONS = {
connectCluster: s__('ClusterAgents|Connect a cluster'),
connectWithAgent: s__('ClusterAgents|Connect a cluster (agent)'),
connectClusterDeprecated: s__('ClusterAgents|Connect a cluster (deprecated)'),
- createClusterDeprecated: s__('ClusterAgents|Create a cluster (deprecated)'),
createCluster: s__('ClusterAgents|Create a cluster'),
- createClusterCertificate: s__('ClusterAgents|Create a cluster (certificate - deprecated)'),
connectClusterCertificate: s__('ClusterAgents|Connect a cluster (certificate - deprecated)'),
- dropdownDisabledHint: s__(
+ actionsDisabledHint: s__(
'ClusterAgents|Requires a Maintainer or greater role to perform these actions',
),
};
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index f6dfb96ffd9..cd334d80e9c 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -23,7 +23,6 @@ export default () => {
defaultBranchName,
projectPath,
kasAddress,
- newClusterPath,
addClusterPath,
newClusterDocsPath,
emptyStateHelpText,
@@ -31,6 +30,7 @@ export default () => {
canAddCluster,
canAdminCluster,
gitlabVersion,
+ kasVersion,
displayClusterAgents,
certificateBasedClustersEnabled,
} = el.dataset;
@@ -42,7 +42,6 @@ export default () => {
emptyStateImage,
projectPath,
kasAddress,
- newClusterPath,
addClusterPath,
newClusterDocsPath,
emptyStateHelpText,
@@ -50,6 +49,7 @@ export default () => {
canAddCluster: parseBoolean(canAddCluster),
canAdminCluster: parseBoolean(canAdminCluster),
gitlabVersion,
+ kasVersion,
displayClusterAgents: parseBoolean(displayClusterAgents),
certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
},
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index b92f3d5a97b..29530ddb7a2 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -212,7 +212,6 @@ export default {
<template #table-header-actions>
<div v-if="canRenderPipelineButton" class="gl-text-right">
<gl-button
- variant="confirm"
data-testid="run_pipeline_button"
:loading="state.isRunningMergeRequestPipeline"
@click="tryRunPipeline"
diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
index 32d9159ee34..af049738016 100644
--- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
+++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
@@ -58,6 +58,7 @@ export default {
}
} else if (this.createBtn) {
this.createBtn.setAttribute('disabled', 'disabled');
+ this.createBtn.classList.add('disabled');
}
},
normalizeProjectData(data) {
@@ -89,11 +90,6 @@ export default {
if (this.warningText) {
this.warningText.classList.remove('gl-display-none');
}
-
- if (this.createBtn) {
- this.createBtn.classList.add('btn-warning');
- this.createBtn.classList.remove('btn-success');
- }
},
},
i18n: {
diff --git a/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue
index 87f22a27856..518ddd7a09c 100644
--- a/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue
@@ -8,11 +8,12 @@ import {
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { BubbleMenu } from '@tiptap/vue-2';
-import codeBlockLanguageLoader from '../services/code_block_language_loader';
-import CodeBlockHighlight from '../extensions/code_block_highlight';
-import Diagram from '../extensions/diagram';
-import Frontmatter from '../extensions/frontmatter';
-import EditorStateObserver from './editor_state_observer.vue';
+import { getParentByTagName } from '~/lib/utils/dom_utils';
+import codeBlockLanguageLoader from '../../services/code_block_language_loader';
+import CodeBlockHighlight from '../../extensions/code_block_highlight';
+import Diagram from '../../extensions/diagram';
+import Frontmatter from '../../extensions/frontmatter';
+import EditorStateObserver from '../editor_state_observer.vue';
const CODE_BLOCK_NODE_TYPES = [CodeBlockHighlight.name, Diagram.name, Frontmatter.name];
@@ -32,6 +33,7 @@ export default {
inject: ['tiptapEditor'],
data() {
return {
+ codeBlockType: undefined,
selectedLanguage: {},
filterTerm: '',
filteredLanguages: [],
@@ -50,47 +52,40 @@ export default {
return CODE_BLOCK_NODE_TYPES.some((type) => editor.isActive(type));
},
- getSelectedLanguage() {
- const { language } = this.tiptapEditor.getAttributes(this.getCodeBlockType());
+ updateSelectedLanguage() {
+ this.codeBlockType = CODE_BLOCK_NODE_TYPES.find((type) => this.tiptapEditor.isActive(type));
- this.selectedLanguage = codeBlockLanguageLoader.findLanguageBySyntax(language);
+ if (this.codeBlockType) {
+ const { language } = this.tiptapEditor.getAttributes(this.codeBlockType);
+ this.selectedLanguage = codeBlockLanguageLoader.findLanguageBySyntax(language);
+ }
},
- async setSelectedLanguage(language) {
- this.selectedLanguage = language;
-
- await codeBlockLanguageLoader.loadLanguages([language.syntax]);
+ copyCodeBlockText() {
+ const { view } = this.tiptapEditor;
+ const { from } = this.tiptapEditor.state.selection;
+ const node = getParentByTagName(view.domAtPos(from).node, 'pre');
- this.tiptapEditor.commands.setCodeBlock({ language: this.selectedLanguage.syntax });
+ navigator.clipboard.writeText(node?.textContent || '');
},
- tippyOnBeforeUpdate(tippy, props) {
- if (props.getReferenceClientRect) {
- // eslint-disable-next-line no-param-reassign
- props.getReferenceClientRect = () => {
- const { view } = this.tiptapEditor;
- const { from } = this.tiptapEditor.state.selection;
+ async applySelectedLanguage(language) {
+ this.selectedLanguage = language;
- for (let { node } = view.domAtPos(from); node; node = node.parentElement) {
- if (node.nodeName?.toLowerCase() === 'pre') {
- return node.getBoundingClientRect();
- }
- }
+ await codeBlockLanguageLoader.loadLanguage(language.syntax);
- return new DOMRect(-1000, -1000, 0, 0);
- };
- }
+ this.tiptapEditor.commands.setCodeBlock({ language: this.selectedLanguage.syntax });
},
- deleteCodeBlock() {
- this.tiptapEditor.chain().focus().deleteNode(this.getCodeBlockType()).run();
+ getReferenceClientRect() {
+ const { view } = this.tiptapEditor;
+ const { from } = this.tiptapEditor.state.selection;
+ const node = getParentByTagName(view.domAtPos(from).node, 'pre');
+ return node?.getBoundingClientRect() || new DOMRect(-1000, -1000, 0, 0);
},
- getCodeBlockType() {
- return (
- CODE_BLOCK_NODE_TYPES.find((type) => this.tiptapEditor.isActive(type)) ||
- CodeBlockHighlight.name
- );
+ deleteCodeBlock() {
+ this.tiptapEditor.chain().focus().deleteNode(this.codeBlockType).run();
},
},
};
@@ -98,15 +93,22 @@ export default {
<template>
<bubble-menu
data-testid="code-block-bubble-menu"
- class="gl-shadow gl-rounded-base"
+ class="gl-shadow gl-rounded-base gl-bg-white"
:editor="tiptapEditor"
plugin-key="bubbleMenuCodeBlock"
:should-show="shouldShow"
- :tippy-options="{ onBeforeUpdate: tippyOnBeforeUpdate }"
+ :tippy-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ getReferenceClientRect,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
>
- <editor-state-observer @transaction="getSelectedLanguage">
+ <editor-state-observer @transaction="updateSelectedLanguage">
<gl-button-group>
- <gl-dropdown contenteditable="false" boundary="viewport" :text="selectedLanguage.label">
+ <gl-dropdown
+ category="tertiary"
+ contenteditable="false"
+ boundary="viewport"
+ :text="selectedLanguage.label"
+ >
<template #header>
<gl-search-box-by-type
v-model="filterTerm"
@@ -125,7 +127,7 @@ export default {
v-for="language in filteredLanguages"
v-show="selectedLanguage.syntax !== language.syntax"
:key="language.syntax"
- @click="setSelectedLanguage(language)"
+ @click="applySelectedLanguage(language)"
>
{{ language.label }}
</gl-dropdown-item>
@@ -133,8 +135,20 @@ export default {
<gl-button
v-gl-tooltip
variant="default"
- category="primary"
+ category="tertiary"
+ size="medium"
+ data-testid="copy-code-block"
+ :aria-label="__('Copy code')"
+ :title="__('Copy code')"
+ icon="copy-to-clipboard"
+ @click="copyCodeBlockText"
+ />
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
size="medium"
+ data-testid="delete-code-block"
:aria-label="__('Delete code block')"
:title="__('Delete code block')"
icon="remove"
diff --git a/app/assets/javascripts/content_editor/components/formatting_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue
index 103079534bc..e35fbf14de5 100644
--- a/app/assets/javascripts/content_editor/components/formatting_bubble_menu.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue
@@ -1,13 +1,16 @@
<script>
import { GlButtonGroup } from '@gitlab/ui';
import { BubbleMenu } from '@tiptap/vue-2';
-import { BUBBLE_MENU_TRACKING_ACTION } from '../constants';
-import trackUIControl from '../services/track_ui_control';
-import Code from '../extensions/code';
-import CodeBlockHighlight from '../extensions/code_block_highlight';
-import Diagram from '../extensions/diagram';
-import Frontmatter from '../extensions/frontmatter';
-import ToolbarButton from './toolbar_button.vue';
+import { BUBBLE_MENU_TRACKING_ACTION } from '../../constants';
+import trackUIControl from '../../services/track_ui_control';
+import Image from '../../extensions/image';
+import Audio from '../../extensions/audio';
+import Video from '../../extensions/video';
+import Code from '../../extensions/code';
+import CodeBlockHighlight from '../../extensions/code_block_highlight';
+import Diagram from '../../extensions/diagram';
+import Frontmatter from '../../extensions/frontmatter';
+import ToolbarButton from '../toolbar_button.vue';
export default {
components: {
@@ -24,7 +27,15 @@ export default {
shouldShow: ({ editor, from, to }) => {
if (from === to) return false;
- const exclude = [Code.name, CodeBlockHighlight.name, Diagram.name, Frontmatter.name];
+ const exclude = [
+ Code.name,
+ CodeBlockHighlight.name,
+ Diagram.name,
+ Frontmatter.name,
+ Image.name,
+ Audio.name,
+ Video.name,
+ ];
return !exclude.some((type) => editor.isActive(type));
},
@@ -34,7 +45,7 @@ export default {
<template>
<bubble-menu
data-testid="formatting-bubble-menu"
- class="gl-shadow gl-rounded-base"
+ class="gl-shadow gl-rounded-base gl-bg-white"
:editor="tiptapEditor"
:should-show="shouldShow"
>
@@ -44,7 +55,7 @@ export default {
content-type="bold"
icon-name="bold"
editor-command="toggleBold"
- category="primary"
+ category="tertiary"
size="medium"
:label="__('Bold text')"
@execute="trackToolbarControlExecution"
@@ -54,7 +65,7 @@ export default {
content-type="italic"
icon-name="italic"
editor-command="toggleItalic"
- category="primary"
+ category="tertiary"
size="medium"
:label="__('Italic text')"
@execute="trackToolbarControlExecution"
@@ -64,7 +75,7 @@ export default {
content-type="strike"
icon-name="strikethrough"
editor-command="toggleStrike"
- category="primary"
+ category="tertiary"
size="medium"
:label="__('Strikethrough')"
@execute="trackToolbarControlExecution"
@@ -74,11 +85,24 @@ export default {
content-type="code"
icon-name="code"
editor-command="toggleCode"
- category="primary"
+ category="tertiary"
size="medium"
:label="__('Code')"
@execute="trackToolbarControlExecution"
/>
+ <toolbar-button
+ data-testid="link"
+ content-type="link"
+ icon-name="link"
+ editor-command="toggleLink"
+ :editor-command-params="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ href: '',
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ category="tertiary"
+ size="medium"
+ :label="__('Insert link')"
+ @execute="trackToolbarControlExecution"
+ />
</gl-button-group>
</bubble-menu>
</template>
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/link.vue b/app/assets/javascripts/content_editor/components/bubble_menus/link.vue
new file mode 100644
index 00000000000..dae0bc63b5a
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/link.vue
@@ -0,0 +1,189 @@
+<script>
+import {
+ GlLink,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlButton,
+ GlButtonGroup,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
+import { BubbleMenu } from '@tiptap/vue-2';
+import Link from '../../extensions/link';
+import EditorStateObserver from '../editor_state_observer.vue';
+
+export default {
+ components: {
+ BubbleMenu,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlLink,
+ GlButton,
+ GlButtonGroup,
+ EditorStateObserver,
+ },
+ directives: {
+ GlTooltip,
+ },
+ inject: ['tiptapEditor', 'contentEditor'],
+ data() {
+ return {
+ linkHref: undefined,
+ linkCanonicalSrc: undefined,
+ linkTitle: undefined,
+
+ isEditing: false,
+ };
+ },
+ watch: {
+ linkCanonicalSrc(value) {
+ if (!value) this.isEditing = true;
+ },
+ },
+ methods: {
+ shouldShow() {
+ const shouldShow = this.tiptapEditor.isActive(Link.name);
+
+ if (!shouldShow) this.isEditing = false;
+
+ return shouldShow;
+ },
+
+ startEditingLink() {
+ // select the entire link
+ this.tiptapEditor.chain().focus().extendMarkRange(Link.name).run();
+
+ this.isEditing = true;
+ },
+
+ async endEditingLink() {
+ this.isEditing = false;
+
+ this.linkHref = await this.contentEditor.resolveUrl(this.linkCanonicalSrc);
+
+ if (!this.linkCanonicalSrc && !this.linkHref) {
+ this.removeLink();
+ }
+ },
+
+ cancelEditingLink() {
+ this.endEditingLink();
+ this.updateLinkToState();
+ },
+
+ async saveEditedLink() {
+ if (!this.linkCanonicalSrc) {
+ this.removeLink();
+ } else {
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .extendMarkRange(Link.name)
+ .updateAttributes(Link.name, {
+ href: this.linkCanonicalSrc,
+ canonicalSrc: this.linkCanonicalSrc,
+ title: this.linkTitle,
+ })
+ .run();
+ }
+
+ this.endEditingLink();
+ },
+
+ updateLinkToState() {
+ if (!this.tiptapEditor.isActive(Link.name)) return;
+
+ const { href, title, canonicalSrc } = this.tiptapEditor.getAttributes(Link.name);
+
+ this.linkTitle = title;
+ this.linkHref = href;
+ this.linkCanonicalSrc = canonicalSrc || href;
+ },
+
+ copyLinkHref() {
+ navigator.clipboard.writeText(this.linkCanonicalSrc);
+ },
+
+ removeLink() {
+ this.tiptapEditor.chain().focus().extendMarkRange(Link.name).unsetLink().run();
+ },
+ },
+};
+</script>
+<template>
+ <bubble-menu
+ data-testid="link-bubble-menu"
+ class="gl-shadow gl-rounded-base gl-bg-white"
+ :editor="tiptapEditor"
+ plugin-key="bubbleMenuLink"
+ :should-show="() => shouldShow()"
+ :tippy-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ placement: 'bottom',
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ >
+ <editor-state-observer @transaction="updateLinkToState">
+ <gl-button-group v-if="!isEditing" class="gl-display-flex gl-align-items-center">
+ <gl-link
+ v-gl-tooltip
+ :href="linkHref"
+ :aria-label="linkCanonicalSrc"
+ :title="linkCanonicalSrc"
+ target="_blank"
+ class="gl-px-3 gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis"
+ >
+ {{ linkCanonicalSrc }}
+ </gl-link>
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="copy-link-url"
+ :aria-label="__('Copy link URL')"
+ :title="__('Copy link URL')"
+ icon="copy-to-clipboard"
+ @click="copyLinkHref"
+ />
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="edit-link"
+ :aria-label="__('Edit link')"
+ :title="__('Edit link')"
+ icon="pencil"
+ @click="startEditingLink"
+ />
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="remove-link"
+ :aria-label="__('Remove link')"
+ :title="__('Remove link')"
+ icon="unlink"
+ @click="removeLink"
+ />
+ </gl-button-group>
+ <gl-form v-else class="bubble-menu-form gl-p-4 gl-w-100" @submit.prevent="saveEditedLink">
+ <gl-form-group :label="__('URL')" label-for="link-href">
+ <gl-form-input id="link-href" v-model="linkCanonicalSrc" data-testid="link-href" />
+ </gl-form-group>
+ <gl-form-group :label="__('Title')" label-for="link-title">
+ <gl-form-input id="link-title" v-model="linkTitle" data-testid="link-title" />
+ </gl-form-group>
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button class="gl-mr-3" data-testid="cancel-link" @click="cancelEditingLink">
+ {{ __('Cancel') }}
+ </gl-button>
+ <gl-button variant="confirm" type="submit">
+ {{ __('Apply') }}
+ </gl-button>
+ </div>
+ </gl-form>
+ </editor-state-observer>
+ </bubble-menu>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/media.vue b/app/assets/javascripts/content_editor/components/bubble_menus/media.vue
new file mode 100644
index 00000000000..a36a860c440
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/media.vue
@@ -0,0 +1,288 @@
+<script>
+import {
+ GlLink,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlLoadingIcon,
+ GlButton,
+ GlButtonGroup,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
+import { BubbleMenu } from '@tiptap/vue-2';
+import { __ } from '~/locale';
+import Audio from '../../extensions/audio';
+import Image from '../../extensions/image';
+import Video from '../../extensions/video';
+import EditorStateObserver from '../editor_state_observer.vue';
+import { acceptedMimes } from '../../services/upload_helpers';
+
+const MEDIA_TYPES = [Audio.name, Image.name, Video.name];
+
+export default {
+ i18n: {
+ copySourceLabels: {
+ [Audio.name]: __('Copy audio URL'),
+ [Image.name]: __('Copy image URL'),
+ [Video.name]: __('Copy video URL'),
+ },
+ editLabels: {
+ [Audio.name]: __('Edit audio description'),
+ [Image.name]: __('Edit image description'),
+ [Video.name]: __('Edit video description'),
+ },
+ replaceLabels: {
+ [Audio.name]: __('Replace audio'),
+ [Image.name]: __('Replace image'),
+ [Video.name]: __('Replace video'),
+ },
+ deleteLabels: {
+ [Audio.name]: __('Delete audio'),
+ [Image.name]: __('Delete image'),
+ [Video.name]: __('Delete video'),
+ },
+ },
+ components: {
+ BubbleMenu,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlLink,
+ GlLoadingIcon,
+ GlButton,
+ GlButtonGroup,
+ EditorStateObserver,
+ },
+ directives: {
+ GlTooltip,
+ },
+ inject: ['tiptapEditor', 'contentEditor'],
+ data() {
+ return {
+ mediaType: undefined,
+ mediaSrc: undefined,
+ mediaCanonicalSrc: undefined,
+ mediaAlt: undefined,
+ mediaTitle: undefined,
+
+ isEditing: false,
+ isUpdating: false,
+ isUploading: false,
+ };
+ },
+ computed: {
+ copySourceLabel() {
+ return this.$options.i18n.copySourceLabels[this.mediaType];
+ },
+ editLabel() {
+ return this.$options.i18n.editLabels[this.mediaType];
+ },
+ replaceLabel() {
+ return this.$options.i18n.replaceLabels[this.mediaType];
+ },
+ deleteLabel() {
+ return this.$options.i18n.deleteLabels[this.mediaType];
+ },
+ showProgressIndicator() {
+ return this.isUploading || this.isUpdating;
+ },
+ },
+ methods: {
+ shouldShow() {
+ const shouldShow = MEDIA_TYPES.some((type) => this.tiptapEditor.isActive(type));
+
+ if (!shouldShow) this.isEditing = false;
+
+ return shouldShow;
+ },
+
+ startEditingMedia() {
+ this.isEditing = true;
+ },
+
+ endEditingMedia() {
+ this.isEditing = false;
+
+ this.updateMediaInfoToState();
+ },
+
+ cancelEditingMedia() {
+ this.endEditingMedia();
+ this.updateMediaInfoToState();
+ },
+
+ async saveEditedMedia() {
+ this.isUpdating = true;
+
+ this.mediaSrc = await this.contentEditor.resolveUrl(this.mediaCanonicalSrc);
+
+ const position = this.tiptapEditor.state.selection.from;
+
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .updateAttributes(this.mediaType, {
+ src: this.mediaSrc,
+ alt: this.mediaAlt,
+ canonicalSrc: this.mediaCanonicalSrc,
+ title: this.mediaTitle,
+ })
+ .run();
+
+ this.tiptapEditor.commands.setNodeSelection(position);
+
+ this.endEditingMedia();
+
+ this.isUpdating = false;
+ },
+
+ async updateMediaInfoToState() {
+ this.mediaType = MEDIA_TYPES.find((type) => this.tiptapEditor.isActive(type));
+
+ if (!this.mediaType) return;
+
+ this.isUpdating = true;
+
+ const { src, title, alt, canonicalSrc, uploading } = this.tiptapEditor.getAttributes(
+ this.mediaType,
+ );
+
+ this.mediaTitle = title;
+ this.mediaAlt = alt;
+ this.mediaCanonicalSrc = canonicalSrc || src;
+ this.isUploading = uploading;
+ this.mediaSrc = await this.contentEditor.resolveUrl(this.mediaCanonicalSrc);
+
+ this.isUpdating = false;
+ },
+
+ replaceMedia() {
+ this.$refs.fileSelector.click();
+ },
+
+ onFileSelect(e) {
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .deleteSelection()
+ .uploadAttachment({
+ file: e.target.files[0],
+ })
+ .run();
+
+ this.$refs.fileSelector.value = '';
+ },
+
+ copyMediaSrc() {
+ navigator.clipboard.writeText(this.mediaCanonicalSrc);
+ },
+
+ deleteMedia() {
+ this.tiptapEditor.chain().focus().deleteSelection().run();
+ },
+ },
+
+ acceptedMimes,
+};
+</script>
+<template>
+ <bubble-menu
+ data-testid="media-bubble-menu"
+ class="gl-shadow gl-rounded-base gl-bg-white"
+ :editor="tiptapEditor"
+ plugin-key="bubbleMenuMedia"
+ :should-show="() => shouldShow()"
+ >
+ <editor-state-observer @transaction="updateMediaInfoToState">
+ <gl-button-group v-if="!isEditing" class="gl-display-flex gl-align-items-center">
+ <gl-loading-icon v-if="showProgressIndicator" class="gl-pl-4 gl-pr-3" />
+ <input
+ ref="fileSelector"
+ type="file"
+ name="content_editor_image"
+ :accept="$options.acceptedMimes[mediaType]"
+ class="gl-display-none"
+ data-qa-selector="file_upload_field"
+ @change="onFileSelect"
+ />
+
+ <gl-link
+ v-if="!showProgressIndicator"
+ v-gl-tooltip
+ :href="mediaSrc"
+ :aria-label="mediaCanonicalSrc"
+ :title="mediaCanonicalSrc"
+ target="_blank"
+ class="gl-px-3 gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis"
+ >
+ {{ mediaCanonicalSrc }}
+ </gl-link>
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="copy-media-src"
+ :aria-label="copySourceLabel"
+ :title="copySourceLabel"
+ icon="copy-to-clipboard"
+ @click="copyMediaSrc"
+ />
+ <gl-button
+ v-if="!showProgressIndicator"
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="edit-media"
+ :aria-label="editLabel"
+ :title="editLabel"
+ icon="pencil"
+ @click="startEditingMedia"
+ />
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="replace-media"
+ :aria-label="replaceLabel"
+ :title="replaceLabel"
+ icon="upload"
+ @click="replaceMedia"
+ />
+ <gl-button
+ v-gl-tooltip
+ variant="default"
+ category="tertiary"
+ size="medium"
+ data-testid="delete-media"
+ :aria-label="deleteLabel"
+ :title="deleteLabel"
+ icon="remove"
+ @click="deleteMedia"
+ />
+ </gl-button-group>
+ <gl-form v-else class="bubble-menu-form gl-p-4 gl-w-100" @submit.prevent="saveEditedMedia">
+ <gl-form-group :label="__('URL')" label-for="media-src">
+ <gl-form-input id="media-src" v-model="mediaCanonicalSrc" data-testid="media-src" />
+ </gl-form-group>
+ <gl-form-group :label="__('Description (alt text)')" label-for="media-alt">
+ <gl-form-input id="media-alt" v-model="mediaAlt" data-testid="media-alt" />
+ </gl-form-group>
+ <gl-form-group :label="__('Title')" label-for="media-title">
+ <gl-form-input id="media-title" v-model="mediaTitle" data-testid="media-title" />
+ </gl-form-group>
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button
+ class="gl-mr-3"
+ data-testid="cancel-editing-media"
+ @click="cancelEditingMedia"
+ >{{ __('Cancel') }}</gl-button
+ >
+ <gl-button variant="confirm" type="submit">{{ __('Apply') }}</gl-button>
+ </div>
+ </gl-form>
+ </editor-state-observer>
+ </bubble-menu>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
index 5b3f4f4ddf2..74ae37b6d06 100644
--- a/app/assets/javascripts/content_editor/components/content_editor.vue
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -4,8 +4,10 @@ import { createContentEditor } from '../services/create_content_editor';
import ContentEditorAlert from './content_editor_alert.vue';
import ContentEditorProvider from './content_editor_provider.vue';
import EditorStateObserver from './editor_state_observer.vue';
-import FormattingBubbleMenu from './formatting_bubble_menu.vue';
-import CodeBlockBubbleMenu from './code_block_bubble_menu.vue';
+import FormattingBubbleMenu from './bubble_menus/formatting.vue';
+import CodeBlockBubbleMenu from './bubble_menus/code_block.vue';
+import LinkBubbleMenu from './bubble_menus/link.vue';
+import MediaBubbleMenu from './bubble_menus/media.vue';
import TopToolbar from './top_toolbar.vue';
import LoadingIndicator from './loading_indicator.vue';
@@ -18,6 +20,8 @@ export default {
TopToolbar,
FormattingBubbleMenu,
CodeBlockBubbleMenu,
+ LinkBubbleMenu,
+ MediaBubbleMenu,
EditorStateObserver,
},
props: {
@@ -92,6 +96,8 @@ export default {
<div class="gl-relative">
<formatting-bubble-menu />
<code-block-bubble-menu />
+ <link-bubble-menu />
+ <media-bubble-menu />
<tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" />
<loading-indicator />
</div>
diff --git a/app/assets/javascripts/content_editor/components/divider.vue b/app/assets/javascripts/content_editor/components/divider.vue
deleted file mode 100644
index b77bd7b7cf3..00000000000
--- a/app/assets/javascripts/content_editor/components/divider.vue
+++ /dev/null
@@ -1,3 +0,0 @@
-<template>
- <span class="gl-mx-3 gl-border-r-solid gl-border-r-1 gl-border-gray-200"></span>
-</template>
diff --git a/app/assets/javascripts/content_editor/components/loading_indicator.vue b/app/assets/javascripts/content_editor/components/loading_indicator.vue
index 620324adb06..7bc953e0dc3 100644
--- a/app/assets/javascripts/content_editor/components/loading_indicator.vue
+++ b/app/assets/javascripts/content_editor/components/loading_indicator.vue
@@ -34,7 +34,7 @@ export default {
class="gl-w-full gl-display-flex gl-justify-content-center gl-align-items-center gl-absolute gl-top-0 gl-bottom-0"
>
<div class="gl-bg-white gl-absolute gl-w-full gl-h-full gl-opacity-3"></div>
- <gl-loading-icon size="md" />
+ <gl-loading-icon size="lg" />
</div>
</editor-state-observer>
</template>
diff --git a/app/assets/javascripts/content_editor/components/toolbar_button.vue b/app/assets/javascripts/content_editor/components/toolbar_button.vue
index cdb877152d4..c16dc34e36f 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_button.vue
@@ -29,6 +29,11 @@ export default {
required: false,
default: '',
},
+ editorCommandParams: {
+ type: Object,
+ required: false,
+ default: null,
+ },
variant: {
type: String,
required: false,
@@ -42,7 +47,7 @@ export default {
size: {
type: String,
required: false,
- default: 'small',
+ default: 'medium',
},
},
data() {
@@ -58,7 +63,7 @@ export default {
const { contentType } = this;
if (this.editorCommand) {
- this.tiptapEditor.chain()[this.editorCommand]().focus().run();
+ this.tiptapEditor.chain()[this.editorCommand](this.editorCommandParams).focus().run();
}
this.$emit('execute', { contentType });
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index 89182b3a09f..19e150a4da9 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -1,6 +1,5 @@
<script>
import trackUIControl from '../services/track_ui_control';
-import Divider from './divider.vue';
import ToolbarButton from './toolbar_button.vue';
import ToolbarImageButton from './toolbar_image_button.vue';
import ToolbarLinkButton from './toolbar_link_button.vue';
@@ -14,7 +13,6 @@ export default {
ToolbarLinkButton,
ToolbarTableButton,
ToolbarImageButton,
- Divider,
},
methods: {
trackToolbarControlExecution({ contentType, value }) {
@@ -25,13 +23,13 @@ export default {
</script>
<template>
<div
- class="gl-display-flex gl-justify-content-end gl-pb-3 gl-pt-0 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200"
+ class="gl-display-flex gl-flex-wrap gl-pb-3 gl-pt-0 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200"
>
<toolbar-text-style-dropdown
data-testid="text-styles"
+ class="gl-mr-3"
@execute="trackToolbarControlExecution"
/>
- <divider />
<toolbar-button
data-testid="bold"
content-type="bold"
@@ -69,7 +67,6 @@ export default {
@execute="trackToolbarControlExecution"
/>
<toolbar-link-button data-testid="link" @execute="trackToolbarControlExecution" />
- <divider />
<toolbar-image-button
ref="imageButton"
data-testid="image"
diff --git a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue
index e8829d00986..1390b9b2daf 100644
--- a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue
+++ b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue
@@ -1,9 +1,10 @@
<script>
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
import { __ } from '~/locale';
+import codeBlockLanguageLoader from '../../services/code_block_language_loader';
export default {
- name: 'FrontMatter',
+ name: 'CodeBlock',
components: {
NodeViewWrapper,
NodeViewContent,
@@ -13,6 +14,16 @@ export default {
type: Object,
required: true,
},
+ updateAttributes: {
+ type: Function,
+ required: true,
+ },
+ },
+ async mounted() {
+ const lang = codeBlockLanguageLoader.findLanguageBySyntax(this.node.attrs.language);
+ await codeBlockLanguageLoader.loadLanguage(lang.syntax);
+
+ this.updateAttributes({ language: this.node.attrs.language });
},
i18n: {
frontmatter: __('frontmatter'),
@@ -22,6 +33,7 @@ export default {
<template>
<node-view-wrapper class="content-editor-code-block gl-relative code highlight" as="pre">
<span
+ v-if="node.attrs.isFrontmatter"
data-testid="frontmatter-label"
class="gl-absolute gl-top-0 gl-right-3"
contenteditable="false"
diff --git a/app/assets/javascripts/content_editor/components/wrappers/media.vue b/app/assets/javascripts/content_editor/components/wrappers/media.vue
deleted file mode 100644
index 37119bdd066..00000000000
--- a/app/assets/javascripts/content_editor/components/wrappers/media.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script>
-import { GlLoadingIcon } from '@gitlab/ui';
-import { NodeViewWrapper } from '@tiptap/vue-2';
-
-const tagNameMap = {
- image: 'img',
- video: 'video',
- audio: 'audio',
-};
-
-export default {
- name: 'MediaWrapper',
- components: {
- NodeViewWrapper,
- GlLoadingIcon,
- },
- props: {
- node: {
- type: Object,
- required: true,
- },
- },
- computed: {
- tagName() {
- return tagNameMap[this.node.type.name] || 'img';
- },
- },
-};
-</script>
-<template>
- <node-view-wrapper class="gl-display-inline-block">
- <span class="gl-relative" :class="{ [`media-container ${tagName}-container`]: true }">
- <gl-loading-icon v-if="node.attrs.uploading" class="gl-absolute gl-left-50p gl-top-half" />
- <component
- :is="tagName"
- data-testid="media"
- :class="{
- 'gl-max-w-full gl-h-auto': tagName !== 'audio',
- 'gl-opacity-5': node.attrs.uploading,
- }"
- :title="node.attrs.title || node.attrs.alt"
- :alt="node.attrs.alt"
- :src="node.attrs.src"
- controls="true"
- />
- <a v-if="tagName !== 'img'" :href="node.attrs.canonicalSrc || node.attrs.src" @click.prevent>
- {{ node.attrs.title || node.attrs.alt }}
- </a>
- </span>
- </node-view-wrapper>
-</template>
diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
index 41c083111c5..209e4629830 100644
--- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
+++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
@@ -124,7 +124,9 @@ export default {
no-caret
text-sr-only
:text="$options.i18n.editTableActions"
- :popper-opts="{ positionFixed: true }"
+ :popper-opts="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ positionFixed: true,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@hide="handleHide($event)"
>
<gl-dropdown-item @click="runCommand('addColumnBefore')">
diff --git a/app/assets/javascripts/content_editor/constants/code_block_languages.js b/app/assets/javascripts/content_editor/constants/code_block_languages.js
new file mode 100644
index 00000000000..1a4dbe4fa22
--- /dev/null
+++ b/app/assets/javascripts/content_editor/constants/code_block_languages.js
@@ -0,0 +1,210 @@
+/* eslint-disable @gitlab/require-i18n-strings */
+// List of languages referenced from https://github.com/wooorm/lowlight#data
+const CODE_BLOCK_LANGUAGES = [
+ { syntax: '1c', label: '1C:Enterprise' },
+ { syntax: 'abnf', label: 'Augmented Backus-Naur Form' },
+ { syntax: 'accesslog', label: 'Apache Access Log' },
+ { syntax: 'actionscript', variants: 'as', label: 'ActionScript' },
+ { syntax: 'ada', label: 'Ada' },
+ { syntax: 'angelscript', variants: 'asc', label: 'AngelScript' },
+ { syntax: 'apache', variants: 'apacheconf', label: 'Apache config' },
+ { syntax: 'applescript', variants: 'osascript', label: 'AppleScript' },
+ { syntax: 'arcade', label: 'ArcGIS Arcade' },
+ { syntax: 'arduino', variants: 'ino', label: 'Arduino' },
+ { syntax: 'armasm', variants: 'arm', label: 'ARM Assembly' },
+ { syntax: 'asciidoc', variants: 'adoc', label: 'AsciiDoc' },
+ { syntax: 'aspectj', label: 'AspectJ' },
+ { syntax: 'autohotkey', variants: 'ahk', label: 'AutoHotkey' },
+ { syntax: 'autoit', label: 'AutoIt' },
+ { syntax: 'avrasm', label: 'AVR Assembly' },
+ { syntax: 'awk', label: 'Awk' },
+ { syntax: 'axapta', variants: 'x++', label: 'X++' },
+ { syntax: 'bash', variants: 'sh', label: 'Bash' },
+ { syntax: 'basic', label: 'BASIC' },
+ { syntax: 'bnf', label: 'Backus-Naur Form' },
+ { syntax: 'brainfuck', variants: 'bf', label: 'Brainfuck' },
+ { syntax: 'c', variants: 'h', label: 'C' },
+ { syntax: 'cal', label: 'C/AL' },
+ { syntax: 'capnproto', variants: 'capnp', label: "Cap'n Proto" },
+ { syntax: 'ceylon', label: 'Ceylon' },
+ { syntax: 'clean', variants: 'icl, dcl', label: 'Clean' },
+ { syntax: 'clojure', variants: 'clj, edn', label: 'Clojure' },
+ { syntax: 'clojure-repl', label: 'Clojure REPL' },
+ { syntax: 'cmake', variants: 'cmake.in', label: 'CMake' },
+ { syntax: 'coffeescript', variants: 'coffee, cson, iced', label: 'CoffeeScript' },
+ { syntax: 'coq', label: 'Coq' },
+ { syntax: 'cos', variants: 'cls', label: 'Caché Object Script' },
+ { syntax: 'cpp', variants: 'cc, c++, h++, hpp, hh, hxx, cxx', label: 'C++' },
+ { syntax: 'crmsh', variants: 'crm, pcmk', label: 'crmsh' },
+ { syntax: 'crystal', variants: 'cr', label: 'Crystal' },
+ { syntax: 'csharp', variants: 'cs, c#', label: 'C#' },
+ { syntax: 'csp', label: 'CSP' },
+ { syntax: 'css', label: 'CSS' },
+ { syntax: 'd', label: 'D' },
+ { syntax: 'dart', label: 'Dart' },
+ { syntax: 'delphi', variants: 'dpr, dfm, pas, pascal', label: 'Delphi' },
+ { syntax: 'diff', variants: 'patch', label: 'Diff' },
+ { syntax: 'django', variants: 'jinja', label: 'Django' },
+ { syntax: 'dns', variants: 'bind, zone', label: 'DNS Zone' },
+ { syntax: 'dockerfile', variants: 'docker', label: 'Dockerfile' },
+ { syntax: 'dos', variants: 'bat, cmd', label: 'Batch file (DOS)' },
+ { syntax: 'dsconfig', label: 'DSConfig' },
+ { syntax: 'dts', label: 'Device Tree' },
+ { syntax: 'dust', variants: 'dst', label: 'Dust' },
+ { syntax: 'ebnf', label: 'Extended Backus-Naur Form' },
+ { syntax: 'elixir', variants: 'ex, exs', label: 'Elixir' },
+ { syntax: 'elm', label: 'Elm' },
+ { syntax: 'erb', label: 'ERB' },
+ { syntax: 'erlang', variants: 'erl', label: 'Erlang' },
+ { syntax: 'erlang-repl', label: 'Erlang REPL' },
+ { syntax: 'excel', variants: 'xlsx, xls', label: 'Excel formulae' },
+ { syntax: 'fix', label: 'FIX' },
+ { syntax: 'flix', label: 'Flix' },
+ { syntax: 'fortran', variants: 'f90, f95', label: 'Fortran' },
+ { syntax: 'fsharp', variants: 'fs, f#', label: 'F#' },
+ { syntax: 'gams', variants: 'gms', label: 'GAMS' },
+ { syntax: 'gauss', variants: 'gss', label: 'GAUSS' },
+ { syntax: 'gcode', variants: 'nc', label: 'G-code (ISO 6983)' },
+ { syntax: 'gherkin', variants: 'feature', label: 'Gherkin' },
+ { syntax: 'glsl', label: 'GLSL' },
+ { syntax: 'gml', label: 'GML' },
+ { syntax: 'go', variants: 'golang', label: 'Go' },
+ { syntax: 'golo', label: 'Golo' },
+ { syntax: 'gradle', label: 'Gradle' },
+ { syntax: 'graphql', variants: 'gql', label: 'GraphQL' },
+ { syntax: 'groovy', label: 'Groovy' },
+ { syntax: 'haml', label: 'HAML' },
+ {
+ syntax: 'handlebars',
+ variants: 'hbs, html.hbs, html.handlebars, htmlbars',
+ label: 'Handlebars',
+ },
+ { syntax: 'haskell', variants: 'hs', label: 'Haskell' },
+ { syntax: 'haxe', variants: 'hx', label: 'Haxe' },
+ { syntax: 'hsp', label: 'HSP' },
+ { syntax: 'http', variants: 'https', label: 'HTTP' },
+ { syntax: 'hy', variants: 'hylang', label: 'Hy' },
+ { syntax: 'inform7', variants: 'i7', label: 'Inform 7' },
+ { syntax: 'ini', variants: 'toml', label: 'TOML, also INI' },
+ { syntax: 'irpf90', label: 'IRPF90' },
+ { syntax: 'isbl', label: 'ISBL' },
+ { syntax: 'java', variants: 'jsp', label: 'Java' },
+ { syntax: 'javascript', variants: 'js, jsx, mjs, cjs', label: 'Javascript' },
+ { syntax: 'jboss-cli', variants: 'wildfly-cli', label: 'JBoss CLI' },
+ { syntax: 'json', label: 'JSON' },
+ { syntax: 'julia', label: 'Julia' },
+ { syntax: 'julia-repl', variants: 'jldoctest', label: 'Julia REPL' },
+ { syntax: 'kotlin', variants: 'kt, kts', label: 'Kotlin' },
+ { syntax: 'lasso', variants: 'ls, lassoscript', label: 'Lasso' },
+ { syntax: 'latex', variants: 'tex', label: 'LaTeX' },
+ { syntax: 'ldif', label: 'LDIF' },
+ { syntax: 'leaf', label: 'Leaf' },
+ { syntax: 'less', label: 'Less' },
+ { syntax: 'lisp', label: 'Lisp' },
+ { syntax: 'livecodeserver', label: 'LiveCode' },
+ { syntax: 'livescript', variants: 'ls', label: 'LiveScript' },
+ { syntax: 'llvm', label: 'LLVM IR' },
+ { syntax: 'lsl', label: 'LSL (Linden Scripting Language)' },
+ { syntax: 'lua', label: 'Lua' },
+ { syntax: 'makefile', variants: 'mk, mak, make', label: 'Makefile' },
+ { syntax: 'markdown', variants: 'md, mkdown, mkd', label: 'Markdown' },
+ { syntax: 'mathematica', variants: 'mma, wl', label: 'Mathematica' },
+ { syntax: 'matlab', label: 'Matlab' },
+ { syntax: 'maxima', label: 'Maxima' },
+ { syntax: 'mel', label: 'MEL' },
+ { syntax: 'mercury', variants: 'm, moo', label: 'Mercury' },
+ { syntax: 'mipsasm', variants: 'mips', label: 'MIPS Assembly' },
+ { syntax: 'mizar', label: 'Mizar' },
+ { syntax: 'mojolicious', label: 'Mojolicious' },
+ { syntax: 'monkey', label: 'Monkey' },
+ { syntax: 'moonscript', variants: 'moon', label: 'MoonScript' },
+ { syntax: 'n1ql', label: 'N1QL' },
+ { syntax: 'nestedtext', variants: 'nt', label: 'Nested Text' },
+ { syntax: 'nginx', variants: 'nginxconf', label: 'Nginx config' },
+ { syntax: 'nim', label: 'Nim' },
+ { syntax: 'nix', variants: 'nixos', label: 'Nix' },
+ { syntax: 'node-repl', label: 'Node REPL' },
+ { syntax: 'nsis', label: 'NSIS' },
+ {
+ syntax: 'objectivec',
+ variants: 'mm, objc, obj-c, obj-c++, objective-c++',
+ label: 'Objective-C',
+ },
+ { syntax: 'ocaml', variants: 'ml', label: 'OCaml' },
+ { syntax: 'openscad', variants: 'scad', label: 'OpenSCAD' },
+ { syntax: 'oxygene', label: 'Oxygene' },
+ { syntax: 'parser3', label: 'Parser3' },
+ { syntax: 'perl', variants: 'pl, pm', label: 'Perl' },
+ { syntax: 'pf', variants: 'pf.conf', label: 'Packet Filter config' },
+ { syntax: 'pgsql', variants: 'postgres, postgresql', label: 'PostgreSQL' },
+ { syntax: 'php', label: 'PHP' },
+ { syntax: 'php-template', label: 'PHP template' },
+ { syntax: 'plaintext', variants: 'text, txt', label: 'Plain text' },
+ { syntax: 'pony', label: 'Pony' },
+ { syntax: 'powershell', variants: 'pwsh, ps, ps1', label: 'PowerShell' },
+ { syntax: 'processing', variants: 'pde', label: 'Processing' },
+ { syntax: 'profile', label: 'Python profiler' },
+ { syntax: 'prolog', label: 'Prolog' },
+ { syntax: 'properties', label: '.properties' },
+ { syntax: 'protobuf', label: 'Protocol Buffers' },
+ { syntax: 'puppet', variants: 'pp', label: 'Puppet' },
+ { syntax: 'purebasic', variants: 'pb, pbi', label: 'PureBASIC' },
+ { syntax: 'python', variants: 'py, gyp, ipython', label: 'Python' },
+ { syntax: 'python-repl', variants: 'pycon', label: 'Python REPL' },
+ { syntax: 'q', variants: 'k, kdb', label: 'Q' },
+ { syntax: 'qml', variants: 'qt', label: 'QML' },
+ { syntax: 'r', label: 'R' },
+ { syntax: 'reasonml', variants: 're', label: 'ReasonML' },
+ { syntax: 'rib', label: 'RenderMan RIB' },
+ { syntax: 'roboconf', variants: 'graph, instances', label: 'Roboconf' },
+ { syntax: 'routeros', variants: 'mikrotik', label: 'Microtik RouterOS script' },
+ { syntax: 'rsl', label: 'RenderMan RSL' },
+ { syntax: 'ruby', variants: 'rb, gemspec, podspec, thor, irb', label: 'Ruby' },
+ { syntax: 'ruleslanguage', label: 'Oracle Rules Language' },
+ { syntax: 'rust', variants: 'rs', label: 'Rust' },
+ { syntax: 'sas', label: 'SAS' },
+ { syntax: 'scala', label: 'Scala' },
+ { syntax: 'scheme', label: 'Scheme' },
+ { syntax: 'scilab', variants: 'sci', label: 'Scilab' },
+ { syntax: 'scss', label: 'SCSS' },
+ { syntax: 'shell', variants: 'console, shellsession', label: 'Shell Session' },
+ { syntax: 'smali', label: 'Smali' },
+ { syntax: 'smalltalk', variants: 'st', label: 'Smalltalk' },
+ { syntax: 'sml', variants: 'ml', label: 'SML (Standard ML)' },
+ { syntax: 'sqf', label: 'SQF' },
+ { syntax: 'sql', label: 'SQL' },
+ { syntax: 'stan', variants: 'stanfuncs', label: 'Stan' },
+ { syntax: 'stata', variants: 'do, ado', label: 'Stata' },
+ { syntax: 'step21', variants: 'p21, step, stp', label: 'STEP Part 21' },
+ { syntax: 'stylus', variants: 'styl', label: 'Stylus' },
+ { syntax: 'subunit', label: 'SubUnit' },
+ { syntax: 'swift', label: 'Swift' },
+ { syntax: 'taggerscript', label: 'Tagger Script' },
+ { syntax: 'tap', label: 'Test Anything Protocol' },
+ { syntax: 'tcl', variants: 'tk', label: 'Tcl' },
+ { syntax: 'thrift', label: 'Thrift' },
+ { syntax: 'tp', label: 'TP' },
+ { syntax: 'twig', variants: 'craftcms', label: 'Twig' },
+ { syntax: 'typescript', variants: 'ts, tsx', label: 'TypeScript' },
+ { syntax: 'vala', label: 'Vala' },
+ { syntax: 'vbnet', variants: 'vb', label: 'Visual Basic .NET' },
+ { syntax: 'vbscript', variants: 'vbs', label: 'VBScript' },
+ { syntax: 'vbscript-html', label: 'VBScript in HTML' },
+ { syntax: 'verilog', variants: 'v, sv, svh', label: 'Verilog' },
+ { syntax: 'vhdl', label: 'VHDL' },
+ { syntax: 'vim', label: 'Vim Script' },
+ { syntax: 'wasm', label: 'WebAssembly' },
+ { syntax: 'wren', label: 'Wren' },
+ { syntax: 'x86asm', label: 'Intel x86 Assembly' },
+ { syntax: 'xl', variants: 'tao', label: 'XL' },
+ {
+ syntax: 'xml',
+ variants: 'html, xhtml, rss, atom, xjb, xsd, xsl, plist, wsf, svg',
+ label: 'HTML, XML',
+ },
+ { syntax: 'xquery', variants: 'xpath, xq', label: 'XQuery' },
+ { syntax: 'yaml', variants: 'yml', label: 'YAML' },
+ { syntax: 'zephir', variants: 'zep', label: 'Zephir' },
+];
+
+export default CODE_BLOCK_LANGUAGES;
diff --git a/app/assets/javascripts/content_editor/constants.js b/app/assets/javascripts/content_editor/constants/index.js
index a39a243ec6b..a39a243ec6b 100644
--- a/app/assets/javascripts/content_editor/constants.js
+++ b/app/assets/javascripts/content_editor/constants/index.js
diff --git a/app/assets/javascripts/content_editor/extensions/blockquote.js b/app/assets/javascripts/content_editor/extensions/blockquote.js
index 5632bc28592..9b424ac8367 100644
--- a/app/assets/javascripts/content_editor/extensions/blockquote.js
+++ b/app/assets/javascripts/content_editor/extensions/blockquote.js
@@ -26,7 +26,7 @@ export default Blockquote.extend({
const multilineInputRegex = /^\s*>>>\s$/gm;
return [
- ...this.parent?.(),
+ ...this.parent(),
wrappingInputRule({
find: multilineInputRegex,
type: this.type,
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
index 61f379fc0a2..cc4ba84a29d 100644
--- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
+++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
@@ -1,6 +1,8 @@
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';
import { textblockTypeInputRule } from '@tiptap/core';
-import codeBlockLanguageLoader from '../services/code_block_language_loader';
+import { VueNodeViewRenderer } from '@tiptap/vue-2';
+import languageLoader from '../services/code_block_language_loader';
+import CodeBlockWrapper from '../components/wrappers/code_block.vue';
const extractLanguage = (element) => element.getAttribute('lang');
export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
@@ -9,14 +11,6 @@ export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
export default CodeBlockLowlight.extend({
isolating: true,
exitOnArrowDown: false,
-
- addOptions() {
- return {
- ...this.parent?.(),
- languageLoader: codeBlockLanguageLoader,
- };
- },
-
addAttributes() {
return {
language: {
@@ -30,7 +24,6 @@ export default CodeBlockLowlight.extend({
};
},
addInputRules() {
- const { languageLoader } = this.options;
const getAttributes = (match) => languageLoader?.loadLanguageFromInputRule(match) || {};
return [
@@ -65,4 +58,8 @@ export default CodeBlockLowlight.extend({
['code', {}, 0],
];
},
+
+ addNodeView() {
+ return new VueNodeViewRenderer(CodeBlockWrapper);
+ },
});
diff --git a/app/assets/javascripts/content_editor/extensions/diagram.js b/app/assets/javascripts/content_editor/extensions/diagram.js
index d192b815092..f9dfeb92e9a 100644
--- a/app/assets/javascripts/content_editor/extensions/diagram.js
+++ b/app/assets/javascripts/content_editor/extensions/diagram.js
@@ -14,6 +14,9 @@ export default CodeBlockHighlight.extend({
return element.dataset.diagram;
},
},
+ isDiagram: {
+ default: true,
+ },
};
},
diff --git a/app/assets/javascripts/content_editor/extensions/frontmatter.js b/app/assets/javascripts/content_editor/extensions/frontmatter.js
index 9842027e192..2ec22158106 100644
--- a/app/assets/javascripts/content_editor/extensions/frontmatter.js
+++ b/app/assets/javascripts/content_editor/extensions/frontmatter.js
@@ -1,10 +1,18 @@
-import { VueNodeViewRenderer } from '@tiptap/vue-2';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
-import FrontmatterWrapper from '../components/wrappers/frontmatter.vue';
import CodeBlockHighlight from './code_block_highlight';
export default CodeBlockHighlight.extend({
name: 'frontmatter',
+
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ isFrontmatter: {
+ default: true,
+ },
+ };
+ },
+
parseHTML() {
return [
{
@@ -24,9 +32,6 @@ export default CodeBlockHighlight.extend({
},
};
},
- addNodeView() {
- return new VueNodeViewRenderer(FrontmatterWrapper);
- },
addInputRules() {
return [];
diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js
index 311db8151cb..25f976f524f 100644
--- a/app/assets/javascripts/content_editor/extensions/image.js
+++ b/app/assets/javascripts/content_editor/extensions/image.js
@@ -1,6 +1,4 @@
import { Image } from '@tiptap/extension-image';
-import { VueNodeViewRenderer } from '@tiptap/vue-2';
-import MediaWrapper from '../components/wrappers/media.vue';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
const resolveImageEl = (element) =>
@@ -77,7 +75,4 @@ export default Image.extend({
},
];
},
- addNodeView() {
- return VueNodeViewRenderer(MediaWrapper);
- },
});
diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
index c349aa42a62..f87e4d8d1dd 100644
--- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js
+++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
@@ -2,7 +2,7 @@ import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state';
import { __ } from '~/locale';
import { VARIANT_DANGER } from '~/flash';
-import createMarkdownDeserializer from '../services/markdown_deserializer';
+import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer';
import {
ALERT_EVENT,
LOADING_CONTENT_EVENT,
@@ -10,10 +10,14 @@ import {
LOADING_ERROR_EVENT,
EXTENSION_PRIORITY_HIGHEST,
} from '../constants';
+import CodeBlockHighlight from './code_block_highlight';
+import Diagram from './diagram';
+import Frontmatter from './frontmatter';
const TEXT_FORMAT = 'text/plain';
const HTML_FORMAT = 'text/html';
const VS_CODE_FORMAT = 'vscode-editor-data';
+const CODE_BLOCK_NODE_TYPES = [CodeBlockHighlight.name, Diagram.name, Frontmatter.name];
export default Extension.create({
name: 'pasteMarkdown',
@@ -75,6 +79,11 @@ export default Extension.create({
return false;
}
+ // if a code block is active, paste as plain text
+ if (CODE_BLOCK_NODE_TYPES.some((type) => this.editor.isActive(type))) {
+ return false;
+ }
+
this.editor.commands.pasteMarkdown(content);
return true;
diff --git a/app/assets/javascripts/content_editor/extensions/playable.js b/app/assets/javascripts/content_editor/extensions/playable.js
index 2c5269377c5..ed343d8acf8 100644
--- a/app/assets/javascripts/content_editor/extensions/playable.js
+++ b/app/assets/javascripts/content_editor/extensions/playable.js
@@ -1,8 +1,6 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Node } from '@tiptap/core';
-import { VueNodeViewRenderer } from '@tiptap/vue-2';
-import MediaWrapper from '../components/wrappers/media.vue';
const queryPlayableElement = (element, mediaType) => element.querySelector(mediaType);
@@ -68,8 +66,4 @@ export default Node.create({
['a', { href: node.attrs.src }, node.attrs.title || node.attrs.alt || ''],
];
},
-
- addNodeView() {
- return VueNodeViewRenderer(MediaWrapper);
- },
});
diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
new file mode 100644
index 00000000000..94236e2e70e
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js
@@ -0,0 +1,48 @@
+import { Extension } from '@tiptap/core';
+import Blockquote from './blockquote';
+import Bold from './bold';
+import BulletList from './bullet_list';
+import Code from './code';
+import CodeBlockHighlight from './code_block_highlight';
+import Heading from './heading';
+import HardBreak from './hard_break';
+import HorizontalRule from './horizontal_rule';
+import Image from './image';
+import Italic from './italic';
+import Link from './link';
+import ListItem from './list_item';
+import OrderedList from './ordered_list';
+import Paragraph from './paragraph';
+
+export default Extension.create({
+ addGlobalAttributes() {
+ return [
+ {
+ types: [
+ Bold.name,
+ Blockquote.name,
+ BulletList.name,
+ Code.name,
+ CodeBlockHighlight.name,
+ HardBreak.name,
+ Heading.name,
+ HorizontalRule.name,
+ Image.name,
+ Italic.name,
+ Link.name,
+ ListItem.name,
+ OrderedList.name,
+ Paragraph.name,
+ ],
+ attributes: {
+ sourceMarkdown: {
+ default: null,
+ },
+ sourceMapKey: {
+ default: null,
+ },
+ },
+ },
+ ];
+ },
+});
diff --git a/app/assets/javascripts/content_editor/services/asset_resolver.js b/app/assets/javascripts/content_editor/services/asset_resolver.js
new file mode 100644
index 00000000000..942457b9664
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/asset_resolver.js
@@ -0,0 +1,13 @@
+import { memoize } from 'lodash';
+
+export default ({ renderMarkdown }) => ({
+ resolveUrl: memoize(async (canonicalSrc) => {
+ const html = await renderMarkdown(`[link](${canonicalSrc})`);
+ if (!html) return canonicalSrc;
+
+ const parser = new DOMParser();
+ const { body } = parser.parseFromString(html, 'text/html');
+
+ return body.querySelector('a').getAttribute('href');
+ }),
+});
diff --git a/app/assets/javascripts/content_editor/services/code_block_language_loader.js b/app/assets/javascripts/content_editor/services/code_block_language_loader.js
index 081400cfd9a..1afaf4bfef6 100644
--- a/app/assets/javascripts/content_editor/services/code_block_language_loader.js
+++ b/app/assets/javascripts/content_editor/services/code_block_language_loader.js
@@ -1,215 +1,7 @@
import { lowlight } from 'lowlight/lib/core';
import { __, sprintf } from '~/locale';
-
-/* eslint-disable @gitlab/require-i18n-strings */
-// List of languages referenced from https://github.com/wooorm/lowlight#data
-const CODE_BLOCK_LANGUAGES = [
- { syntax: '1c', label: '1C:Enterprise' },
- { syntax: 'abnf', label: 'Augmented Backus-Naur Form' },
- { syntax: 'accesslog', label: 'Apache Access Log' },
- { syntax: 'actionscript', variants: 'as', label: 'ActionScript' },
- { syntax: 'ada', label: 'Ada' },
- { syntax: 'angelscript', variants: 'asc', label: 'AngelScript' },
- { syntax: 'apache', variants: 'apacheconf', label: 'Apache config' },
- { syntax: 'applescript', variants: 'osascript', label: 'AppleScript' },
- { syntax: 'arcade', label: 'ArcGIS Arcade' },
- { syntax: 'arduino', variants: 'ino', label: 'Arduino' },
- { syntax: 'armasm', variants: 'arm', label: 'ARM Assembly' },
- { syntax: 'asciidoc', variants: 'adoc', label: 'AsciiDoc' },
- { syntax: 'aspectj', label: 'AspectJ' },
- { syntax: 'autohotkey', variants: 'ahk', label: 'AutoHotkey' },
- { syntax: 'autoit', label: 'AutoIt' },
- { syntax: 'avrasm', label: 'AVR Assembly' },
- { syntax: 'awk', label: 'Awk' },
- { syntax: 'axapta', variants: 'x++', label: 'X++' },
- { syntax: 'bash', variants: 'sh', label: 'Bash' },
- { syntax: 'basic', label: 'BASIC' },
- { syntax: 'bnf', label: 'Backus-Naur Form' },
- { syntax: 'brainfuck', variants: 'bf', label: 'Brainfuck' },
- { syntax: 'c', variants: 'h', label: 'C' },
- { syntax: 'cal', label: 'C/AL' },
- { syntax: 'capnproto', variants: 'capnp', label: "Cap'n Proto" },
- { syntax: 'ceylon', label: 'Ceylon' },
- { syntax: 'clean', variants: 'icl, dcl', label: 'Clean' },
- { syntax: 'clojure', variants: 'clj, edn', label: 'Clojure' },
- { syntax: 'clojure-repl', label: 'Clojure REPL' },
- { syntax: 'cmake', variants: 'cmake.in', label: 'CMake' },
- { syntax: 'coffeescript', variants: 'coffee, cson, iced', label: 'CoffeeScript' },
- { syntax: 'coq', label: 'Coq' },
- { syntax: 'cos', variants: 'cls', label: 'Caché Object Script' },
- { syntax: 'cpp', variants: 'cc, c++, h++, hpp, hh, hxx, cxx', label: 'C++' },
- { syntax: 'crmsh', variants: 'crm, pcmk', label: 'crmsh' },
- { syntax: 'crystal', variants: 'cr', label: 'Crystal' },
- { syntax: 'csharp', variants: 'cs, c#', label: 'C#' },
- { syntax: 'csp', label: 'CSP' },
- { syntax: 'css', label: 'CSS' },
- { syntax: 'd', label: 'D' },
- { syntax: 'dart', label: 'Dart' },
- { syntax: 'delphi', variants: 'dpr, dfm, pas, pascal', label: 'Delphi' },
- { syntax: 'diff', variants: 'patch', label: 'Diff' },
- { syntax: 'django', variants: 'jinja', label: 'Django' },
- { syntax: 'dns', variants: 'bind, zone', label: 'DNS Zone' },
- { syntax: 'dockerfile', variants: 'docker', label: 'Dockerfile' },
- { syntax: 'dos', variants: 'bat, cmd', label: 'Batch file (DOS)' },
- { syntax: 'dsconfig', label: 'DSConfig' },
- { syntax: 'dts', label: 'Device Tree' },
- { syntax: 'dust', variants: 'dst', label: 'Dust' },
- { syntax: 'ebnf', label: 'Extended Backus-Naur Form' },
- { syntax: 'elixir', variants: 'ex, exs', label: 'Elixir' },
- { syntax: 'elm', label: 'Elm' },
- { syntax: 'erb', label: 'ERB' },
- { syntax: 'erlang', variants: 'erl', label: 'Erlang' },
- { syntax: 'erlang-repl', label: 'Erlang REPL' },
- { syntax: 'excel', variants: 'xlsx, xls', label: 'Excel formulae' },
- { syntax: 'fix', label: 'FIX' },
- { syntax: 'flix', label: 'Flix' },
- { syntax: 'fortran', variants: 'f90, f95', label: 'Fortran' },
- { syntax: 'fsharp', variants: 'fs, f#', label: 'F#' },
- { syntax: 'gams', variants: 'gms', label: 'GAMS' },
- { syntax: 'gauss', variants: 'gss', label: 'GAUSS' },
- { syntax: 'gcode', variants: 'nc', label: 'G-code (ISO 6983)' },
- { syntax: 'gherkin', variants: 'feature', label: 'Gherkin' },
- { syntax: 'glsl', label: 'GLSL' },
- { syntax: 'gml', label: 'GML' },
- { syntax: 'go', variants: 'golang', label: 'Go' },
- { syntax: 'golo', label: 'Golo' },
- { syntax: 'gradle', label: 'Gradle' },
- { syntax: 'graphql', variants: 'gql', label: 'GraphQL' },
- { syntax: 'groovy', label: 'Groovy' },
- { syntax: 'haml', label: 'HAML' },
- {
- syntax: 'handlebars',
- variants: 'hbs, html.hbs, html.handlebars, htmlbars',
- label: 'Handlebars',
- },
- { syntax: 'haskell', variants: 'hs', label: 'Haskell' },
- { syntax: 'haxe', variants: 'hx', label: 'Haxe' },
- { syntax: 'hsp', label: 'HSP' },
- { syntax: 'http', variants: 'https', label: 'HTTP' },
- { syntax: 'hy', variants: 'hylang', label: 'Hy' },
- { syntax: 'inform7', variants: 'i7', label: 'Inform 7' },
- { syntax: 'ini', variants: 'toml', label: 'TOML, also INI' },
- { syntax: 'irpf90', label: 'IRPF90' },
- { syntax: 'isbl', label: 'ISBL' },
- { syntax: 'java', variants: 'jsp', label: 'Java' },
- { syntax: 'javascript', variants: 'js, jsx, mjs, cjs', label: 'Javascript' },
- { syntax: 'jboss-cli', variants: 'wildfly-cli', label: 'JBoss CLI' },
- { syntax: 'json', label: 'JSON' },
- { syntax: 'julia', label: 'Julia' },
- { syntax: 'julia-repl', variants: 'jldoctest', label: 'Julia REPL' },
- { syntax: 'kotlin', variants: 'kt, kts', label: 'Kotlin' },
- { syntax: 'lasso', variants: 'ls, lassoscript', label: 'Lasso' },
- { syntax: 'latex', variants: 'tex', label: 'LaTeX' },
- { syntax: 'ldif', label: 'LDIF' },
- { syntax: 'leaf', label: 'Leaf' },
- { syntax: 'less', label: 'Less' },
- { syntax: 'lisp', label: 'Lisp' },
- { syntax: 'livecodeserver', label: 'LiveCode' },
- { syntax: 'livescript', variants: 'ls', label: 'LiveScript' },
- { syntax: 'llvm', label: 'LLVM IR' },
- { syntax: 'lsl', label: 'LSL (Linden Scripting Language)' },
- { syntax: 'lua', label: 'Lua' },
- { syntax: 'makefile', variants: 'mk, mak, make', label: 'Makefile' },
- { syntax: 'markdown', variants: 'md, mkdown, mkd', label: 'Markdown' },
- { syntax: 'mathematica', variants: 'mma, wl', label: 'Mathematica' },
- { syntax: 'matlab', label: 'Matlab' },
- { syntax: 'maxima', label: 'Maxima' },
- { syntax: 'mel', label: 'MEL' },
- { syntax: 'mercury', variants: 'm, moo', label: 'Mercury' },
- { syntax: 'mipsasm', variants: 'mips', label: 'MIPS Assembly' },
- { syntax: 'mizar', label: 'Mizar' },
- { syntax: 'mojolicious', label: 'Mojolicious' },
- { syntax: 'monkey', label: 'Monkey' },
- { syntax: 'moonscript', variants: 'moon', label: 'MoonScript' },
- { syntax: 'n1ql', label: 'N1QL' },
- { syntax: 'nestedtext', variants: 'nt', label: 'Nested Text' },
- { syntax: 'nginx', variants: 'nginxconf', label: 'Nginx config' },
- { syntax: 'nim', label: 'Nim' },
- { syntax: 'nix', variants: 'nixos', label: 'Nix' },
- { syntax: 'node-repl', label: 'Node REPL' },
- { syntax: 'nsis', label: 'NSIS' },
- {
- syntax: 'objectivec',
- variants: 'mm, objc, obj-c, obj-c++, objective-c++',
- label: 'Objective-C',
- },
- { syntax: 'ocaml', variants: 'ml', label: 'OCaml' },
- { syntax: 'openscad', variants: 'scad', label: 'OpenSCAD' },
- { syntax: 'oxygene', label: 'Oxygene' },
- { syntax: 'parser3', label: 'Parser3' },
- { syntax: 'perl', variants: 'pl, pm', label: 'Perl' },
- { syntax: 'pf', variants: 'pf.conf', label: 'Packet Filter config' },
- { syntax: 'pgsql', variants: 'postgres, postgresql', label: 'PostgreSQL' },
- { syntax: 'php', label: 'PHP' },
- { syntax: 'php-template', label: 'PHP template' },
- { syntax: 'plaintext', variants: 'text, txt', label: 'Plain text' },
- { syntax: 'pony', label: 'Pony' },
- { syntax: 'powershell', variants: 'pwsh, ps, ps1', label: 'PowerShell' },
- { syntax: 'processing', variants: 'pde', label: 'Processing' },
- { syntax: 'profile', label: 'Python profiler' },
- { syntax: 'prolog', label: 'Prolog' },
- { syntax: 'properties', label: '.properties' },
- { syntax: 'protobuf', label: 'Protocol Buffers' },
- { syntax: 'puppet', variants: 'pp', label: 'Puppet' },
- { syntax: 'purebasic', variants: 'pb, pbi', label: 'PureBASIC' },
- { syntax: 'python', variants: 'py, gyp, ipython', label: 'Python' },
- { syntax: 'python-repl', variants: 'pycon', label: 'Python REPL' },
- { syntax: 'q', variants: 'k, kdb', label: 'Q' },
- { syntax: 'qml', variants: 'qt', label: 'QML' },
- { syntax: 'r', label: 'R' },
- { syntax: 'reasonml', variants: 're', label: 'ReasonML' },
- { syntax: 'rib', label: 'RenderMan RIB' },
- { syntax: 'roboconf', variants: 'graph, instances', label: 'Roboconf' },
- { syntax: 'routeros', variants: 'mikrotik', label: 'Microtik RouterOS script' },
- { syntax: 'rsl', label: 'RenderMan RSL' },
- { syntax: 'ruby', variants: 'rb, gemspec, podspec, thor, irb', label: 'Ruby' },
- { syntax: 'ruleslanguage', label: 'Oracle Rules Language' },
- { syntax: 'rust', variants: 'rs', label: 'Rust' },
- { syntax: 'sas', label: 'SAS' },
- { syntax: 'scala', label: 'Scala' },
- { syntax: 'scheme', label: 'Scheme' },
- { syntax: 'scilab', variants: 'sci', label: 'Scilab' },
- { syntax: 'scss', label: 'SCSS' },
- { syntax: 'shell', variants: 'console, shellsession', label: 'Shell Session' },
- { syntax: 'smali', label: 'Smali' },
- { syntax: 'smalltalk', variants: 'st', label: 'Smalltalk' },
- { syntax: 'sml', variants: 'ml', label: 'SML (Standard ML)' },
- { syntax: 'sqf', label: 'SQF' },
- { syntax: 'sql', label: 'SQL' },
- { syntax: 'stan', variants: 'stanfuncs', label: 'Stan' },
- { syntax: 'stata', variants: 'do, ado', label: 'Stata' },
- { syntax: 'step21', variants: 'p21, step, stp', label: 'STEP Part 21' },
- { syntax: 'stylus', variants: 'styl', label: 'Stylus' },
- { syntax: 'subunit', label: 'SubUnit' },
- { syntax: 'swift', label: 'Swift' },
- { syntax: 'taggerscript', label: 'Tagger Script' },
- { syntax: 'tap', label: 'Test Anything Protocol' },
- { syntax: 'tcl', variants: 'tk', label: 'Tcl' },
- { syntax: 'thrift', label: 'Thrift' },
- { syntax: 'tp', label: 'TP' },
- { syntax: 'twig', variants: 'craftcms', label: 'Twig' },
- { syntax: 'typescript', variants: 'ts, tsx', label: 'TypeScript' },
- { syntax: 'vala', label: 'Vala' },
- { syntax: 'vbnet', variants: 'vb', label: 'Visual Basic .NET' },
- { syntax: 'vbscript', variants: 'vbs', label: 'VBScript' },
- { syntax: 'vbscript-html', label: 'VBScript in HTML' },
- { syntax: 'verilog', variants: 'v, sv, svh', label: 'Verilog' },
- { syntax: 'vhdl', label: 'VHDL' },
- { syntax: 'vim', label: 'Vim Script' },
- { syntax: 'wasm', label: 'WebAssembly' },
- { syntax: 'wren', label: 'Wren' },
- { syntax: 'x86asm', label: 'Intel x86 Assembly' },
- { syntax: 'xl', variants: 'tao', label: 'XL' },
- {
- syntax: 'xml',
- variants: 'html, xhtml, rss, atom, xjb, xsd, xsl, plist, wsf, svg',
- label: 'HTML, XML',
- },
- { syntax: 'xquery', variants: 'xpath, xq', label: 'XQuery' },
- { syntax: 'yaml', variants: 'yml', label: 'YAML' },
- { syntax: 'zephir', variants: 'zep', label: 'Zephir' },
-];
-/* eslint-enable @gitlab/require-i18n-strings */
+import CODE_BLOCK_LANGUAGES from '../constants/code_block_languages';
+import languageLoader from './highlight_js_language_loader';
const codeBlockLanguageLoader = {
lowlight,
@@ -245,38 +37,24 @@ const codeBlockLanguageLoader = {
return this.lowlight.registered(language);
},
- loadLanguagesFromDOM(domTree) {
- const languages = [];
-
- domTree.querySelectorAll('pre').forEach((preElement) => {
- languages.push(preElement.getAttribute('lang'));
- });
-
- return this.loadLanguages(languages);
- },
-
loadLanguageFromInputRule(match) {
const { syntax } = this.findLanguageBySyntax(match[1]);
- this.loadLanguages([syntax]);
+ this.loadLanguage(syntax);
return { language: syntax };
},
- loadLanguages(languageList = []) {
- const loaders = languageList
- .filter((languageName) => !this.isLanguageLoaded(languageName))
- .map((languageName) => {
- return import(
- /* webpackChunkName: 'highlight.language.js' */ `highlight.js/lib/languages/${languageName}`
- )
- .then(({ default: language }) => {
- this.lowlight.registerLanguage(languageName, language);
- })
- .catch(() => false);
- });
+ async loadLanguage(languageName) {
+ if (this.isLanguageLoaded(languageName)) return false;
- return Promise.all(loaders);
+ try {
+ const { default: language } = await languageLoader[languageName]();
+ this.lowlight.registerLanguage(languageName, language);
+ return true;
+ } catch {
+ return false;
+ }
},
};
diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js
index 56badf965ee..52dacb84153 100644
--- a/app/assets/javascripts/content_editor/services/content_editor.js
+++ b/app/assets/javascripts/content_editor/services/content_editor.js
@@ -3,12 +3,13 @@ import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } fro
/* eslint-disable no-underscore-dangle */
export class ContentEditor {
- constructor({ tiptapEditor, serializer, deserializer, eventHub, languageLoader }) {
+ constructor({ tiptapEditor, serializer, deserializer, assetResolver, eventHub }) {
this._tiptapEditor = tiptapEditor;
this._serializer = serializer;
this._deserializer = deserializer;
this._eventHub = eventHub;
- this._languageLoader = languageLoader;
+ this._assetResolver = assetResolver;
+ this._pristineDoc = null;
}
get tiptapEditor() {
@@ -19,6 +20,10 @@ export class ContentEditor {
return this._eventHub;
}
+ get changed() {
+ return this._pristineDoc?.eq(this.tiptapEditor.state.doc);
+ }
+
get empty() {
const doc = this.tiptapEditor?.state.doc;
@@ -34,28 +39,30 @@ export class ContentEditor {
this._eventHub.dispose();
}
+ deserialize(serializedContent) {
+ const { _tiptapEditor: editor, _deserializer: deserializer } = this;
+
+ return deserializer.deserialize({
+ schema: editor.schema,
+ content: serializedContent,
+ });
+ }
+
+ resolveUrl(canonicalSrc) {
+ return this._assetResolver.resolveUrl(canonicalSrc);
+ }
+
async setSerializedContent(serializedContent) {
- const {
- _tiptapEditor: editor,
- _deserializer: deserializer,
- _eventHub: eventHub,
- _languageLoader: languageLoader,
- } = this;
+ const { _tiptapEditor: editor, _eventHub: eventHub } = this;
const { doc, tr } = editor.state;
const selection = TextSelection.create(doc, 0, doc.content.size);
try {
eventHub.$emit(LOADING_CONTENT_EVENT);
- const result = await deserializer.deserialize({
- schema: editor.schema,
- content: serializedContent,
- });
-
- if (Object.keys(result).length !== 0) {
- const { document, dom } = result;
-
- await languageLoader.loadLanguagesFromDOM(dom);
+ const { document } = await this.deserialize(serializedContent);
+ if (document) {
+ this._pristineDoc = document;
tr.setSelection(selection)
.replaceSelectionWith(document, false)
.setMeta('preventUpdate', true);
@@ -70,8 +77,9 @@ export class ContentEditor {
}
getSerializedContent() {
- const { _tiptapEditor: editor, _serializer: serializer } = this;
+ const { _tiptapEditor: editor, _serializer: serializer, _pristineDoc: pristineDoc } = this;
+ const { doc } = editor.state;
- return serializer.serialize({ schema: editor.schema, content: editor.getJSON() });
+ return serializer.serialize({ doc, pristineDoc });
}
}
diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js
index af19a0ab0e4..15aac3d86e5 100644
--- a/app/assets/javascripts/content_editor/services/create_content_editor.js
+++ b/app/assets/javascripts/content_editor/services/create_content_editor.js
@@ -43,6 +43,7 @@ import OrderedList from '../extensions/ordered_list';
import Paragraph from '../extensions/paragraph';
import PasteMarkdown from '../extensions/paste_markdown';
import Reference from '../extensions/reference';
+import Sourcemap from '../extensions/sourcemap';
import Strike from '../extensions/strike';
import Subscript from '../extensions/subscript';
import Superscript from '../extensions/superscript';
@@ -58,9 +59,10 @@ import Video from '../extensions/video';
import WordBreak from '../extensions/word_break';
import { ContentEditor } from './content_editor';
import createMarkdownSerializer from './markdown_serializer';
-import createMarkdownDeserializer from './markdown_deserializer';
+import createGlApiMarkdownDeserializer from './gl_api_markdown_deserializer';
+import createRemarkMarkdownDeserializer from './remark_markdown_deserializer';
+import createAssetResolver from './asset_resolver';
import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts';
-import languageLoader from './code_block_language_loader';
const createTiptapEditor = ({ extensions = [], ...options } = {}) =>
new Editor({
@@ -94,7 +96,7 @@ export const createContentEditor = ({
BulletList,
Code,
ColorChip,
- CodeBlockHighlight.configure({ lowlight, languageLoader }),
+ CodeBlockHighlight.configure({ lowlight }),
DescriptionItem,
DescriptionList,
Details,
@@ -127,6 +129,7 @@ export const createContentEditor = ({
Paragraph,
PasteMarkdown.configure({ renderMarkdown, eventHub }),
Reference,
+ Sourcemap,
Strike,
Subscript,
Superscript,
@@ -146,7 +149,18 @@ export const createContentEditor = ({
const trackedExtensions = allExtensions.map(trackInputRulesAndShortcuts);
const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions });
const serializer = createMarkdownSerializer({ serializerConfig });
- const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
+ const deserializer = window.gon?.features?.preserveUnchangedMarkdown
+ ? createRemarkMarkdownDeserializer()
+ : createGlApiMarkdownDeserializer({
+ render: renderMarkdown,
+ });
+ const assetResolver = createAssetResolver({ renderMarkdown });
- return new ContentEditor({ tiptapEditor, serializer, eventHub, deserializer, languageLoader });
+ return new ContentEditor({
+ tiptapEditor,
+ serializer,
+ eventHub,
+ deserializer,
+ assetResolver,
+ });
};
diff --git a/app/assets/javascripts/content_editor/services/markdown_deserializer.js b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js
index cd4863d8eac..dcd56e55268 100644
--- a/app/assets/javascripts/content_editor/services/markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js
@@ -27,7 +27,7 @@ export default ({ render }) => {
// append original source as a comment that nodes can access
body.append(document.createComment(content));
- return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), dom: body };
+ return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body) };
},
};
};
diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
new file mode 100644
index 00000000000..b6a3e0bc26a
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
@@ -0,0 +1,475 @@
+/**
+ * This module implements a function that converts a Hast Abstract
+ * Syntax Tree (AST) to a ProseMirror document.
+ *
+ * It is based on the prosemirror-markdown’s from_markdown module
+ * https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js.
+ *
+ * It deviates significantly from the original because
+ * prosemirror-markdown supports converting an markdown-it AST instead of a
+ * HAST one. It also adds sourcemap attributes automatically to every
+ * ProseMirror node and mark created during the conversion process.
+ *
+ * We recommend becoming familiar with HAST and ProseMirror documents to
+ * facilitate the understanding of the behavior implemented in this module.
+ *
+ * Unist syntax tree documentation: https://github.com/syntax-tree/unist
+ * Hast tree documentation: https://github.com/syntax-tree/hast
+ * ProseMirror document documentation: https://prosemirror.net/docs/ref/#model.Document_Structure
+ * visit-parents documentation: https://github.com/syntax-tree/unist-util-visit-parents
+ */
+
+import { Mark } from 'prosemirror-model';
+import { visitParents } from 'unist-util-visit-parents';
+import { toString } from 'hast-util-to-string';
+import { isFunction } from 'lodash';
+
+/**
+ * Merges two ProseMirror text nodes if both text nodes
+ * have the same set of marks.
+ *
+ * @param {ProseMirror.Node} a first ProseMirror node
+ * @param {ProseMirror.Node} b second ProseMirror node
+ * @returns {model.Node} A new text node that results from combining
+ * the text of the two text node parameters or null.
+ */
+function maybeMerge(a, b) {
+ if (a && a.isText && b && b.isText && Mark.sameSet(a.marks, b.marks)) {
+ return a.withText(a.text + b.text);
+ }
+
+ return null;
+}
+
+/**
+ * Creates an object that contains sourcemap position information
+ * included in a Hast Abstract Syntax Tree. The Content
+ * Editor uses the sourcemap information to restore the
+ * original source of a node when the user doesn’t change it.
+ *
+ * Unist syntax tree documentation: https://github.com/syntax-tree/unist
+ * Hast node documentation: https://github.com/syntax-tree/hast
+ *
+ * @param {HastNode} hastNode A Hast node
+ * @param {String} source Markdown source file
+ *
+ * @returns It returns an object with the following attributes:
+ *
+ * - sourceMapKey: A string that uniquely identifies what is
+ * the position of the hast node in the Markdown source file.
+ * - sourceMarkdown: A node’s original Markdown source extrated
+ * from the Markdown source file.
+ */
+function createSourceMapAttributes(hastNode, source) {
+ const { position } = hastNode;
+
+ return {
+ sourceMapKey: `${position.start.offset}:${position.end.offset}`,
+ sourceMarkdown: source.substring(position.start.offset, position.end.offset),
+ };
+}
+
+/**
+ * Compute ProseMirror node’s attributes from a Hast node.
+ * By default, this function includes sourcemap position
+ * information in the object returned.
+ *
+ * Other attributes are retrieved by invoking a getAttrs
+ * function provided by the ProseMirror node factory spec.
+ *
+ * @param {*} proseMirrorNodeSpec ProseMirror node spec object
+ * @param {HastNode} hastNode A hast node
+ * @param {Array<HastNode>} hastParents All the ancestors of the hastNode
+ * @param {String} source Markdown source file’s content
+ *
+ * @returns An object that contains a ProseMirror node’s attributes
+ */
+function getAttrs(proseMirrorNodeSpec, hastNode, hastParents, source) {
+ const { getAttrs: specGetAttrs } = proseMirrorNodeSpec;
+
+ return {
+ ...createSourceMapAttributes(hastNode, source),
+ ...(isFunction(specGetAttrs) ? specGetAttrs(hastNode, hastParents, source) : {}),
+ };
+}
+
+/**
+ * Keeps track of the Hast -> ProseMirror conversion process.
+ *
+ * When the `openNode` method is invoked, it adds the node to a stack
+ * data structure. When the `closeNode` method is invoked, it removes the
+ * last element from the Stack, creates a ProseMirror node, and adds that
+ * ProseMirror node to the previous node in the Stack.
+ *
+ * For example, given a Hast tree with three levels of nodes:
+ *
+ * - blockquote
+ * - paragraph
+ * - text
+ *
+ * 3. text
+ * 2. paragraph
+ * 1. blockquote
+ *
+ * Calling `closeNode` will fold the text node into paragraph. A 2nd
+ * call to this method will fold "paragraph" into "blockquote".
+ *
+ * Mark state
+ *
+ * When the `openMark` method is invoked, this class adds the Mark to a `MarkSet`
+ * object. When a text node is added, it assigns all the opened marks to that text
+ * node and cleans the marks. It takes care of merging text nodes with the same
+ * set of marks as well.
+ */
+class HastToProseMirrorConverterState {
+ constructor() {
+ this.stack = [];
+ this.marks = Mark.none;
+ }
+
+ /**
+ * Gets the first element of the node stack
+ */
+ get top() {
+ return this.stack[this.stack.length - 1];
+ }
+
+ /**
+ * Detects if the node stack is empty
+ */
+ get empty() {
+ return this.stack.length === 0;
+ }
+
+ /**
+ * Creates a text node and adds it to
+ * the top node in the stack.
+ *
+ * It applies the marks stored temporarily
+ * by calling the `addMark` method. After
+ * the text node is added, it clears the mark
+ * set afterward.
+ *
+ * If the top block node has a text
+ * node with the same set of marks as the
+ * text node created, this method merges
+ * both text nodes
+ *
+ * @param {ProseMirror.Schema} schema ProseMirror schema
+ * @param {String} text Text
+ * @returns
+ */
+ addText(schema, text) {
+ if (!text) return;
+ const nodes = this.top.content;
+ const last = nodes[nodes.length - 1];
+ const node = schema.text(text, this.marks);
+ const merged = maybeMerge(last, node);
+
+ if (last && merged) {
+ nodes[nodes.length - 1] = merged;
+ } else {
+ nodes.push(node);
+ }
+
+ this.closeMarks();
+ }
+
+ /**
+ * Adds a mark to the set of marks stored temporarily
+ * until addText is called.
+ * @param {*} markType
+ * @param {*} attrs
+ */
+ openMark(markType, attrs) {
+ this.marks = markType.create(attrs).addToSet(this.marks);
+ }
+
+ /**
+ * Empties the temporary Mark set.
+ */
+ closeMarks() {
+ this.marks = Mark.none;
+ }
+
+ /**
+ * Adds a node to the stack data structure.
+ *
+ * @param {Schema.NodeType} type ProseMirror Schema for the node
+ * @param {HastNode} hastNode Hast node from which the ProseMirror node will be created
+ * @param {*} attrs Node’s attributes
+ * @param {*} factorySpec The factory spec used to create the node factory
+ */
+ openNode(type, hastNode, attrs, factorySpec) {
+ this.stack.push({ type, attrs, content: [], hastNode, factorySpec });
+ }
+
+ /**
+ * Removes the top ProseMirror node from the
+ * conversion stack and adds the node to the
+ * previous element.
+ * @returns
+ */
+ closeNode() {
+ const { type, attrs, content } = this.stack.pop();
+ const node = type.createAndFill(attrs, content);
+
+ if (!node) return null;
+
+ if (this.marks.length) {
+ this.marks = Mark.none;
+ }
+
+ if (!this.empty) {
+ this.top.content.push(node);
+ }
+
+ return node;
+ }
+
+ closeUntil(hastNode) {
+ while (hastNode !== this.top?.hastNode) {
+ this.closeNode();
+ }
+ }
+}
+
+/**
+ * Create ProseMirror node/mark factories based on one or more
+ * factory specifications.
+ *
+ * Note: Read `createProseMirrorDocFromMdastTree` documentation
+ * for instructions about how to define these specifications.
+ *
+ * @param {model.ProseMirrorSchema} schema A ProseMirror schema used to create the
+ * ProseMirror nodes and marks.
+ * @param {Object} proseMirrorFactorySpecs ProseMirror nodes factory specifications.
+ * @param {String} source Markdown source file’s content
+ *
+ * @returns An object that contains ProseMirror node factories
+ */
+const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source) => {
+ const handlers = {
+ root: (state, hastNode) => state.openNode(schema.topNodeType, hastNode, {}),
+ text: (state, hastNode) => {
+ const { factorySpec } = state.top;
+
+ if (/^\s+$/.test(hastNode.value)) {
+ return;
+ }
+
+ if (factorySpec.wrapTextInParagraph === true) {
+ state.openNode(schema.nodeType('paragraph'));
+ state.addText(schema, hastNode.value);
+ state.closeNode();
+ } else {
+ state.addText(schema, hastNode.value);
+ }
+ },
+ };
+
+ for (const [hastNodeTagName, factorySpec] of Object.entries(proseMirrorFactorySpecs)) {
+ if (factorySpec.block) {
+ handlers[hastNodeTagName] = (state, hastNode, parent, ancestors) => {
+ const nodeType = schema.nodeType(
+ isFunction(factorySpec.block)
+ ? factorySpec.block(hastNode, parent, ancestors)
+ : factorySpec.block,
+ );
+
+ state.closeUntil(parent);
+ state.openNode(
+ nodeType,
+ hastNode,
+ getAttrs(factorySpec, hastNode, parent, source),
+ factorySpec,
+ );
+
+ /**
+ * If a getContent function is provided, we immediately close
+ * the node to delegate content processing to this function.
+ * */
+ if (isFunction(factorySpec.getContent)) {
+ state.addText(
+ schema,
+ factorySpec.getContent({ hastNode, hastNodeText: toString(hastNode) }),
+ );
+ state.closeNode();
+ }
+ };
+ } else if (factorySpec.inline) {
+ const nodeType = schema.nodeType(factorySpec.inline);
+ handlers[hastNodeTagName] = (state, hastNode, parent) => {
+ state.closeUntil(parent);
+ state.openNode(
+ nodeType,
+ hastNode,
+ getAttrs(factorySpec, hastNode, parent, source),
+ factorySpec,
+ );
+ // Inline nodes do not have children therefore they are immediately closed
+ state.closeNode();
+ };
+ } else if (factorySpec.mark) {
+ const markType = schema.marks[factorySpec.mark];
+ handlers[hastNodeTagName] = (state, hastNode, parent) => {
+ state.openMark(markType, getAttrs(factorySpec, hastNode, parent, source));
+
+ if (factorySpec.inlineContent) {
+ state.addText(schema, hastNode.value);
+ }
+ };
+ } else {
+ throw new RangeError(`Unrecognized node factory spec ${JSON.stringify(factorySpec)}`);
+ }
+ }
+
+ return handlers;
+};
+
+/**
+ * Converts a Hast AST to a ProseMirror document based on a series
+ * of specifications that describe how to map all the nodes of the former
+ * to ProseMirror nodes or marks.
+ *
+ * The specification object describes how to map a Hast node to a ProseMirror node or mark.
+ * The converter will trigger an error if it doesn’t find a specification
+ * for a Hast node while traversing the AST.
+ *
+ * The object should have the following shape:
+ *
+ * {
+ * [hastNode.tagName]: {
+ * [block|node|mark]: [ProseMirror.Node.name],
+ * ...configurationOptions
+ * }
+ * }
+ *
+ * Where each property in the object represents a HAST node with a given tag name, for example:
+ *
+ * {
+ * h1: {},
+ * h2: {},
+ * table: {},
+ * strong: {},
+ * // etc
+ * }
+ *
+ * You can specify the type of ProseMirror object adding one the following
+ * properties:
+ *
+ * 1. "block": A ProseMirror node that contains one or more children.
+ * 2. "inline": A ProseMirror node that doesn’t contain any children although
+ * it can have inline content like a code block or a reference.
+ * 3. "mark": A ProseMirror mark.
+ *
+ * The value of that property should be the name of the ProseMirror node or mark, i.e:
+ *
+ * {
+ * h1: {
+ * block: 'heading',
+ * },
+ * h2: {
+ * block: 'heading',
+ * },
+ * img: {
+ * node: 'image',
+ * },
+ * strong: {
+ * mark: 'bold',
+ * }
+ * }
+ *
+ * You can compute a ProseMirror’s node or mark name based on the HAST node
+ * by passing a function instead of a String. The converter invokes the function
+ * and provides a HAST node object:
+ *
+ * {
+ * list: {
+ * block: (hastNode) => {
+ * let type = 'bulletList';
+
+ * if (hastNode.children.some(isTaskItem)) {
+ * type = 'taskList';
+ * } else if (hastNode.ordered) {
+ * type = 'orderedList';
+ * }
+
+ * return type;
+ * }
+ * }
+ * }
+ *
+ * Configuration options
+ * ----------------------
+ *
+ * You can customize the conversion process for every node or mark
+ * setting the following properties in the specification object:
+ *
+ * **getAttrs**
+ *
+ * Computes a ProseMirror node or mark attributes. The converter will invoke
+ * `getAttrs` with the following parameters:
+ *
+ * 1. hastNode: The hast node
+ * 2. hasParents: All the hast node’s ancestors up to the root node
+ * 3. source: Markdown source file’s content
+ *
+ * **wrapTextInParagraph**
+ *
+ * This property only applies to block nodes. If a block node contains text,
+ * it will wrap that text in a paragraph. This is useful for ProseMirror block
+ * nodes that don’t allow text directly such as list items and tables.
+ *
+ * **skipChildren**
+ *
+ * Skips a hast node’s children while traversing the tree.
+ *
+ * **getContent**
+ *
+ * Allows to pass a custom function that returns the content of a block node. The
+ * Content is limited to a single text node therefore the function should return
+ * a String value.
+ *
+ * Use this property along skipChildren to provide custom processing of child nodes
+ * for a block node.
+ *
+ * @param {model.Document_Schema} params.schema A ProseMirror schema that specifies the shape
+ * of the ProseMirror document.
+ * @param {Object} params.factorySpec A factory specification as described above
+ * @param {Hast} params.tree https://github.com/syntax-tree/hast
+ * @param {String} params.source Markdown source from which the MDast tree was generated
+ *
+ * @returns A ProseMirror document
+ */
+export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree, source }) => {
+ const proseMirrorNodeFactories = createProseMirrorNodeFactories(schema, factorySpecs, source);
+ const state = new HastToProseMirrorConverterState();
+
+ visitParents(tree, (hastNode, ancestors) => {
+ const parent = ancestors[ancestors.length - 1];
+ const skipChildren = factorySpecs[hastNode.tagName]?.skipChildren;
+
+ const handler = proseMirrorNodeFactories[hastNode.tagName || hastNode.type];
+
+ if (!handler) {
+ throw new Error(
+ `Hast node of type "${
+ hastNode.tagName || hastNode.type
+ }" not supported by this converter. Please, provide an specification.`,
+ );
+ }
+
+ handler(state, hastNode, parent, ancestors);
+
+ return skipChildren === true ? 'skip' : true;
+ });
+
+ let doc;
+
+ do {
+ doc = state.closeNode();
+ } while (!state.empty);
+
+ return doc;
+};
diff --git a/app/assets/javascripts/content_editor/services/highlight_js_language_loader.js b/app/assets/javascripts/content_editor/services/highlight_js_language_loader.js
new file mode 100644
index 00000000000..a0ebbebed4e
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/highlight_js_language_loader.js
@@ -0,0 +1,248 @@
+/**
+ * This file is generated based on the contents of highlight.js/lib/languages to avoid
+ * utilizing dynamic expressions within `import()` which were the source of some
+ * confusion when attempting to produce deterministic webpack compilations across
+ * multiple build environments.
+ *
+ * This list of highlight-able languages will need to be updated as new options are
+ * introduced within the highlight.js dependency.
+ */
+
+export default {
+ '1c': () => import(/* webpackChunkName: 'hl-1c' */ 'highlight.js/lib/languages/1c'),
+ abnf: () => import(/* webpackChunkName: 'hl-abnf' */ 'highlight.js/lib/languages/abnf'),
+ accesslog: () =>
+ import(/* webpackChunkName: 'hl-accesslog' */ 'highlight.js/lib/languages/accesslog'),
+ actionscript: () =>
+ import(/* webpackChunkName: 'hl-actionscript' */ 'highlight.js/lib/languages/actionscript'),
+ ada: () => import(/* webpackChunkName: 'hl-ada' */ 'highlight.js/lib/languages/ada'),
+ angelscript: () =>
+ import(/* webpackChunkName: 'hl-angelscript' */ 'highlight.js/lib/languages/angelscript'),
+ apache: () => import(/* webpackChunkName: 'hl-apache' */ 'highlight.js/lib/languages/apache'),
+ applescript: () =>
+ import(/* webpackChunkName: 'hl-applescript' */ 'highlight.js/lib/languages/applescript'),
+ arcade: () => import(/* webpackChunkName: 'hl-arcade' */ 'highlight.js/lib/languages/arcade'),
+ arduino: () => import(/* webpackChunkName: 'hl-arduino' */ 'highlight.js/lib/languages/arduino'),
+ armasm: () => import(/* webpackChunkName: 'hl-armasm' */ 'highlight.js/lib/languages/armasm'),
+ asciidoc: () =>
+ import(/* webpackChunkName: 'hl-asciidoc' */ 'highlight.js/lib/languages/asciidoc'),
+ aspectj: () => import(/* webpackChunkName: 'hl-aspectj' */ 'highlight.js/lib/languages/aspectj'),
+ autohotkey: () =>
+ import(/* webpackChunkName: 'hl-autohotkey' */ 'highlight.js/lib/languages/autohotkey'),
+ autoit: () => import(/* webpackChunkName: 'hl-autoit' */ 'highlight.js/lib/languages/autoit'),
+ avrasm: () => import(/* webpackChunkName: 'hl-avrasm' */ 'highlight.js/lib/languages/avrasm'),
+ awk: () => import(/* webpackChunkName: 'hl-awk' */ 'highlight.js/lib/languages/awk'),
+ axapta: () => import(/* webpackChunkName: 'hl-axapta' */ 'highlight.js/lib/languages/axapta'),
+ bash: () => import(/* webpackChunkName: 'hl-bash' */ 'highlight.js/lib/languages/bash'),
+ basic: () => import(/* webpackChunkName: 'hl-basic' */ 'highlight.js/lib/languages/basic'),
+ bnf: () => import(/* webpackChunkName: 'hl-bnf' */ 'highlight.js/lib/languages/bnf'),
+ brainfuck: () =>
+ import(/* webpackChunkName: 'hl-brainfuck' */ 'highlight.js/lib/languages/brainfuck'),
+ c: () => import(/* webpackChunkName: 'hl-c' */ 'highlight.js/lib/languages/c'),
+ cal: () => import(/* webpackChunkName: 'hl-cal' */ 'highlight.js/lib/languages/cal'),
+ capnproto: () =>
+ import(/* webpackChunkName: 'hl-capnproto' */ 'highlight.js/lib/languages/capnproto'),
+ ceylon: () => import(/* webpackChunkName: 'hl-ceylon' */ 'highlight.js/lib/languages/ceylon'),
+ clean: () => import(/* webpackChunkName: 'hl-clean' */ 'highlight.js/lib/languages/clean'),
+ 'clojure-repl': () =>
+ import(/* webpackChunkName: 'hl-clojure-repl' */ 'highlight.js/lib/languages/clojure-repl'),
+ clojure: () => import(/* webpackChunkName: 'hl-clojure' */ 'highlight.js/lib/languages/clojure'),
+ cmake: () => import(/* webpackChunkName: 'hl-cmake' */ 'highlight.js/lib/languages/cmake'),
+ coffeescript: () =>
+ import(/* webpackChunkName: 'hl-coffeescript' */ 'highlight.js/lib/languages/coffeescript'),
+ coq: () => import(/* webpackChunkName: 'hl-coq' */ 'highlight.js/lib/languages/coq'),
+ cos: () => import(/* webpackChunkName: 'hl-cos' */ 'highlight.js/lib/languages/cos'),
+ cpp: () => import(/* webpackChunkName: 'hl-cpp' */ 'highlight.js/lib/languages/cpp'),
+ crmsh: () => import(/* webpackChunkName: 'hl-crmsh' */ 'highlight.js/lib/languages/crmsh'),
+ crystal: () => import(/* webpackChunkName: 'hl-crystal' */ 'highlight.js/lib/languages/crystal'),
+ csharp: () => import(/* webpackChunkName: 'hl-csharp' */ 'highlight.js/lib/languages/csharp'),
+ csp: () => import(/* webpackChunkName: 'hl-csp' */ 'highlight.js/lib/languages/csp'),
+ css: () => import(/* webpackChunkName: 'hl-css' */ 'highlight.js/lib/languages/css'),
+ d: () => import(/* webpackChunkName: 'hl-d' */ 'highlight.js/lib/languages/d'),
+ dart: () => import(/* webpackChunkName: 'hl-dart' */ 'highlight.js/lib/languages/dart'),
+ delphi: () => import(/* webpackChunkName: 'hl-delphi' */ 'highlight.js/lib/languages/delphi'),
+ diff: () => import(/* webpackChunkName: 'hl-diff' */ 'highlight.js/lib/languages/diff'),
+ django: () => import(/* webpackChunkName: 'hl-django' */ 'highlight.js/lib/languages/django'),
+ dns: () => import(/* webpackChunkName: 'hl-dns' */ 'highlight.js/lib/languages/dns'),
+ dockerfile: () =>
+ import(/* webpackChunkName: 'hl-dockerfile' */ 'highlight.js/lib/languages/dockerfile'),
+ dos: () => import(/* webpackChunkName: 'hl-dos' */ 'highlight.js/lib/languages/dos'),
+ dsconfig: () =>
+ import(/* webpackChunkName: 'hl-dsconfig' */ 'highlight.js/lib/languages/dsconfig'),
+ dts: () => import(/* webpackChunkName: 'hl-dts' */ 'highlight.js/lib/languages/dts'),
+ dust: () => import(/* webpackChunkName: 'hl-dust' */ 'highlight.js/lib/languages/dust'),
+ ebnf: () => import(/* webpackChunkName: 'hl-ebnf' */ 'highlight.js/lib/languages/ebnf'),
+ elixir: () => import(/* webpackChunkName: 'hl-elixir' */ 'highlight.js/lib/languages/elixir'),
+ elm: () => import(/* webpackChunkName: 'hl-elm' */ 'highlight.js/lib/languages/elm'),
+ erb: () => import(/* webpackChunkName: 'hl-erb' */ 'highlight.js/lib/languages/erb'),
+ 'erlang-repl': () =>
+ import(/* webpackChunkName: 'hl-erlang-repl' */ 'highlight.js/lib/languages/erlang-repl'),
+ erlang: () => import(/* webpackChunkName: 'hl-erlang' */ 'highlight.js/lib/languages/erlang'),
+ excel: () => import(/* webpackChunkName: 'hl-excel' */ 'highlight.js/lib/languages/excel'),
+ fix: () => import(/* webpackChunkName: 'hl-fix' */ 'highlight.js/lib/languages/fix'),
+ flix: () => import(/* webpackChunkName: 'hl-flix' */ 'highlight.js/lib/languages/flix'),
+ fortran: () => import(/* webpackChunkName: 'hl-fortran' */ 'highlight.js/lib/languages/fortran'),
+ fsharp: () => import(/* webpackChunkName: 'hl-fsharp' */ 'highlight.js/lib/languages/fsharp'),
+ gams: () => import(/* webpackChunkName: 'hl-gams' */ 'highlight.js/lib/languages/gams'),
+ gauss: () => import(/* webpackChunkName: 'hl-gauss' */ 'highlight.js/lib/languages/gauss'),
+ gcode: () => import(/* webpackChunkName: 'hl-gcode' */ 'highlight.js/lib/languages/gcode'),
+ gherkin: () => import(/* webpackChunkName: 'hl-gherkin' */ 'highlight.js/lib/languages/gherkin'),
+ glsl: () => import(/* webpackChunkName: 'hl-glsl' */ 'highlight.js/lib/languages/glsl'),
+ gml: () => import(/* webpackChunkName: 'hl-gml' */ 'highlight.js/lib/languages/gml'),
+ go: () => import(/* webpackChunkName: 'hl-go' */ 'highlight.js/lib/languages/go'),
+ golo: () => import(/* webpackChunkName: 'hl-golo' */ 'highlight.js/lib/languages/golo'),
+ gradle: () => import(/* webpackChunkName: 'hl-gradle' */ 'highlight.js/lib/languages/gradle'),
+ groovy: () => import(/* webpackChunkName: 'hl-groovy' */ 'highlight.js/lib/languages/groovy'),
+ haml: () => import(/* webpackChunkName: 'hl-haml' */ 'highlight.js/lib/languages/haml'),
+ handlebars: () =>
+ import(/* webpackChunkName: 'hl-handlebars' */ 'highlight.js/lib/languages/handlebars'),
+ haskell: () => import(/* webpackChunkName: 'hl-haskell' */ 'highlight.js/lib/languages/haskell'),
+ haxe: () => import(/* webpackChunkName: 'hl-haxe' */ 'highlight.js/lib/languages/haxe'),
+ hsp: () => import(/* webpackChunkName: 'hl-hsp' */ 'highlight.js/lib/languages/hsp'),
+ http: () => import(/* webpackChunkName: 'hl-http' */ 'highlight.js/lib/languages/http'),
+ hy: () => import(/* webpackChunkName: 'hl-hy' */ 'highlight.js/lib/languages/hy'),
+ inform7: () => import(/* webpackChunkName: 'hl-inform7' */ 'highlight.js/lib/languages/inform7'),
+ ini: () => import(/* webpackChunkName: 'hl-ini' */ 'highlight.js/lib/languages/ini'),
+ irpf90: () => import(/* webpackChunkName: 'hl-irpf90' */ 'highlight.js/lib/languages/irpf90'),
+ isbl: () => import(/* webpackChunkName: 'hl-isbl' */ 'highlight.js/lib/languages/isbl'),
+ java: () => import(/* webpackChunkName: 'hl-java' */ 'highlight.js/lib/languages/java'),
+ javascript: () =>
+ import(/* webpackChunkName: 'hl-javascript' */ 'highlight.js/lib/languages/javascript'),
+ 'jboss-cli': () =>
+ import(/* webpackChunkName: 'hl-jboss-cli' */ 'highlight.js/lib/languages/jboss-cli'),
+ json: () => import(/* webpackChunkName: 'hl-json' */ 'highlight.js/lib/languages/json'),
+ 'julia-repl': () =>
+ import(/* webpackChunkName: 'hl-julia-repl' */ 'highlight.js/lib/languages/julia-repl'),
+ julia: () => import(/* webpackChunkName: 'hl-julia' */ 'highlight.js/lib/languages/julia'),
+ kotlin: () => import(/* webpackChunkName: 'hl-kotlin' */ 'highlight.js/lib/languages/kotlin'),
+ lasso: () => import(/* webpackChunkName: 'hl-lasso' */ 'highlight.js/lib/languages/lasso'),
+ latex: () => import(/* webpackChunkName: 'hl-latex' */ 'highlight.js/lib/languages/latex'),
+ ldif: () => import(/* webpackChunkName: 'hl-ldif' */ 'highlight.js/lib/languages/ldif'),
+ leaf: () => import(/* webpackChunkName: 'hl-leaf' */ 'highlight.js/lib/languages/leaf'),
+ less: () => import(/* webpackChunkName: 'hl-less' */ 'highlight.js/lib/languages/less'),
+ lisp: () => import(/* webpackChunkName: 'hl-lisp' */ 'highlight.js/lib/languages/lisp'),
+ livecodeserver: () =>
+ import(/* webpackChunkName: 'hl-livecodeserver' */ 'highlight.js/lib/languages/livecodeserver'),
+ livescript: () =>
+ import(/* webpackChunkName: 'hl-livescript' */ 'highlight.js/lib/languages/livescript'),
+ llvm: () => import(/* webpackChunkName: 'hl-llvm' */ 'highlight.js/lib/languages/llvm'),
+ lsl: () => import(/* webpackChunkName: 'hl-lsl' */ 'highlight.js/lib/languages/lsl'),
+ lua: () => import(/* webpackChunkName: 'hl-lua' */ 'highlight.js/lib/languages/lua'),
+ makefile: () =>
+ import(/* webpackChunkName: 'hl-makefile' */ 'highlight.js/lib/languages/makefile'),
+ markdown: () =>
+ import(/* webpackChunkName: 'hl-markdown' */ 'highlight.js/lib/languages/markdown'),
+ mathematica: () =>
+ import(/* webpackChunkName: 'hl-mathematica' */ 'highlight.js/lib/languages/mathematica'),
+ matlab: () => import(/* webpackChunkName: 'hl-matlab' */ 'highlight.js/lib/languages/matlab'),
+ maxima: () => import(/* webpackChunkName: 'hl-maxima' */ 'highlight.js/lib/languages/maxima'),
+ mel: () => import(/* webpackChunkName: 'hl-mel' */ 'highlight.js/lib/languages/mel'),
+ mercury: () => import(/* webpackChunkName: 'hl-mercury' */ 'highlight.js/lib/languages/mercury'),
+ mipsasm: () => import(/* webpackChunkName: 'hl-mipsasm' */ 'highlight.js/lib/languages/mipsasm'),
+ mizar: () => import(/* webpackChunkName: 'hl-mizar' */ 'highlight.js/lib/languages/mizar'),
+ mojolicious: () =>
+ import(/* webpackChunkName: 'hl-mojolicious' */ 'highlight.js/lib/languages/mojolicious'),
+ monkey: () => import(/* webpackChunkName: 'hl-monkey' */ 'highlight.js/lib/languages/monkey'),
+ moonscript: () =>
+ import(/* webpackChunkName: 'hl-moonscript' */ 'highlight.js/lib/languages/moonscript'),
+ n1ql: () => import(/* webpackChunkName: 'hl-n1ql' */ 'highlight.js/lib/languages/n1ql'),
+ nestedtext: () =>
+ import(/* webpackChunkName: 'hl-nestedtext' */ 'highlight.js/lib/languages/nestedtext'),
+ nginx: () => import(/* webpackChunkName: 'hl-nginx' */ 'highlight.js/lib/languages/nginx'),
+ nim: () => import(/* webpackChunkName: 'hl-nim' */ 'highlight.js/lib/languages/nim'),
+ nix: () => import(/* webpackChunkName: 'hl-nix' */ 'highlight.js/lib/languages/nix'),
+ 'node-repl': () =>
+ import(/* webpackChunkName: 'hl-node-repl' */ 'highlight.js/lib/languages/node-repl'),
+ nsis: () => import(/* webpackChunkName: 'hl-nsis' */ 'highlight.js/lib/languages/nsis'),
+ objectivec: () =>
+ import(/* webpackChunkName: 'hl-objectivec' */ 'highlight.js/lib/languages/objectivec'),
+ ocaml: () => import(/* webpackChunkName: 'hl-ocaml' */ 'highlight.js/lib/languages/ocaml'),
+ openscad: () =>
+ import(/* webpackChunkName: 'hl-openscad' */ 'highlight.js/lib/languages/openscad'),
+ oxygene: () => import(/* webpackChunkName: 'hl-oxygene' */ 'highlight.js/lib/languages/oxygene'),
+ parser3: () => import(/* webpackChunkName: 'hl-parser3' */ 'highlight.js/lib/languages/parser3'),
+ perl: () => import(/* webpackChunkName: 'hl-perl' */ 'highlight.js/lib/languages/perl'),
+ pf: () => import(/* webpackChunkName: 'hl-pf' */ 'highlight.js/lib/languages/pf'),
+ pgsql: () => import(/* webpackChunkName: 'hl-pgsql' */ 'highlight.js/lib/languages/pgsql'),
+ 'php-template': () =>
+ import(/* webpackChunkName: 'hl-php-template' */ 'highlight.js/lib/languages/php-template'),
+ php: () => import(/* webpackChunkName: 'hl-php' */ 'highlight.js/lib/languages/php'),
+ plaintext: () =>
+ import(/* webpackChunkName: 'hl-plaintext' */ 'highlight.js/lib/languages/plaintext'),
+ pony: () => import(/* webpackChunkName: 'hl-pony' */ 'highlight.js/lib/languages/pony'),
+ powershell: () =>
+ import(/* webpackChunkName: 'hl-powershell' */ 'highlight.js/lib/languages/powershell'),
+ processing: () =>
+ import(/* webpackChunkName: 'hl-processing' */ 'highlight.js/lib/languages/processing'),
+ profile: () => import(/* webpackChunkName: 'hl-profile' */ 'highlight.js/lib/languages/profile'),
+ prolog: () => import(/* webpackChunkName: 'hl-prolog' */ 'highlight.js/lib/languages/prolog'),
+ properties: () =>
+ import(/* webpackChunkName: 'hl-properties' */ 'highlight.js/lib/languages/properties'),
+ protobuf: () =>
+ import(/* webpackChunkName: 'hl-protobuf' */ 'highlight.js/lib/languages/protobuf'),
+ puppet: () => import(/* webpackChunkName: 'hl-puppet' */ 'highlight.js/lib/languages/puppet'),
+ purebasic: () =>
+ import(/* webpackChunkName: 'hl-purebasic' */ 'highlight.js/lib/languages/purebasic'),
+ 'python-repl': () =>
+ import(/* webpackChunkName: 'hl-python-repl' */ 'highlight.js/lib/languages/python-repl'),
+ python: () => import(/* webpackChunkName: 'hl-python' */ 'highlight.js/lib/languages/python'),
+ q: () => import(/* webpackChunkName: 'hl-q' */ 'highlight.js/lib/languages/q'),
+ qml: () => import(/* webpackChunkName: 'hl-qml' */ 'highlight.js/lib/languages/qml'),
+ r: () => import(/* webpackChunkName: 'hl-r' */ 'highlight.js/lib/languages/r'),
+ reasonml: () =>
+ import(/* webpackChunkName: 'hl-reasonml' */ 'highlight.js/lib/languages/reasonml'),
+ rib: () => import(/* webpackChunkName: 'hl-rib' */ 'highlight.js/lib/languages/rib'),
+ roboconf: () =>
+ import(/* webpackChunkName: 'hl-roboconf' */ 'highlight.js/lib/languages/roboconf'),
+ routeros: () =>
+ import(/* webpackChunkName: 'hl-routeros' */ 'highlight.js/lib/languages/routeros'),
+ rsl: () => import(/* webpackChunkName: 'hl-rsl' */ 'highlight.js/lib/languages/rsl'),
+ ruby: () => import(/* webpackChunkName: 'hl-ruby' */ 'highlight.js/lib/languages/ruby'),
+ ruleslanguage: () =>
+ import(/* webpackChunkName: 'hl-ruleslanguage' */ 'highlight.js/lib/languages/ruleslanguage'),
+ rust: () => import(/* webpackChunkName: 'hl-rust' */ 'highlight.js/lib/languages/rust'),
+ sas: () => import(/* webpackChunkName: 'hl-sas' */ 'highlight.js/lib/languages/sas'),
+ scala: () => import(/* webpackChunkName: 'hl-scala' */ 'highlight.js/lib/languages/scala'),
+ scheme: () => import(/* webpackChunkName: 'hl-scheme' */ 'highlight.js/lib/languages/scheme'),
+ scilab: () => import(/* webpackChunkName: 'hl-scilab' */ 'highlight.js/lib/languages/scilab'),
+ scss: () => import(/* webpackChunkName: 'hl-scss' */ 'highlight.js/lib/languages/scss'),
+ shell: () => import(/* webpackChunkName: 'hl-shell' */ 'highlight.js/lib/languages/shell'),
+ smali: () => import(/* webpackChunkName: 'hl-smali' */ 'highlight.js/lib/languages/smali'),
+ smalltalk: () =>
+ import(/* webpackChunkName: 'hl-smalltalk' */ 'highlight.js/lib/languages/smalltalk'),
+ sml: () => import(/* webpackChunkName: 'hl-sml' */ 'highlight.js/lib/languages/sml'),
+ sqf: () => import(/* webpackChunkName: 'hl-sqf' */ 'highlight.js/lib/languages/sqf'),
+ sql: () => import(/* webpackChunkName: 'hl-sql' */ 'highlight.js/lib/languages/sql'),
+ stan: () => import(/* webpackChunkName: 'hl-stan' */ 'highlight.js/lib/languages/stan'),
+ stata: () => import(/* webpackChunkName: 'hl-stata' */ 'highlight.js/lib/languages/stata'),
+ step21: () => import(/* webpackChunkName: 'hl-step21' */ 'highlight.js/lib/languages/step21'),
+ stylus: () => import(/* webpackChunkName: 'hl-stylus' */ 'highlight.js/lib/languages/stylus'),
+ subunit: () => import(/* webpackChunkName: 'hl-subunit' */ 'highlight.js/lib/languages/subunit'),
+ swift: () => import(/* webpackChunkName: 'hl-swift' */ 'highlight.js/lib/languages/swift'),
+ taggerscript: () =>
+ import(/* webpackChunkName: 'hl-taggerscript' */ 'highlight.js/lib/languages/taggerscript'),
+ tap: () => import(/* webpackChunkName: 'hl-tap' */ 'highlight.js/lib/languages/tap'),
+ tcl: () => import(/* webpackChunkName: 'hl-tcl' */ 'highlight.js/lib/languages/tcl'),
+ thrift: () => import(/* webpackChunkName: 'hl-thrift' */ 'highlight.js/lib/languages/thrift'),
+ tp: () => import(/* webpackChunkName: 'hl-tp' */ 'highlight.js/lib/languages/tp'),
+ twig: () => import(/* webpackChunkName: 'hl-twig' */ 'highlight.js/lib/languages/twig'),
+ typescript: () =>
+ import(/* webpackChunkName: 'hl-typescript' */ 'highlight.js/lib/languages/typescript'),
+ vala: () => import(/* webpackChunkName: 'hl-vala' */ 'highlight.js/lib/languages/vala'),
+ vbnet: () => import(/* webpackChunkName: 'hl-vbnet' */ 'highlight.js/lib/languages/vbnet'),
+ 'vbscript-html': () =>
+ import(/* webpackChunkName: 'hl-vbscript-html' */ 'highlight.js/lib/languages/vbscript-html'),
+ vbscript: () =>
+ import(/* webpackChunkName: 'hl-vbscript' */ 'highlight.js/lib/languages/vbscript'),
+ verilog: () => import(/* webpackChunkName: 'hl-verilog' */ 'highlight.js/lib/languages/verilog'),
+ vhdl: () => import(/* webpackChunkName: 'hl-vhdl' */ 'highlight.js/lib/languages/vhdl'),
+ vim: () => import(/* webpackChunkName: 'hl-vim' */ 'highlight.js/lib/languages/vim'),
+ wasm: () => import(/* webpackChunkName: 'hl-wasm' */ 'highlight.js/lib/languages/wasm'),
+ wren: () => import(/* webpackChunkName: 'hl-wren' */ 'highlight.js/lib/languages/wren'),
+ x86asm: () => import(/* webpackChunkName: 'hl-x86asm' */ 'highlight.js/lib/languages/x86asm'),
+ xl: () => import(/* webpackChunkName: 'hl-xl' */ 'highlight.js/lib/languages/xl'),
+ xml: () => import(/* webpackChunkName: 'hl-xml' */ 'highlight.js/lib/languages/xml'),
+ xquery: () => import(/* webpackChunkName: 'hl-xquery' */ 'highlight.js/lib/languages/xquery'),
+ yaml: () => import(/* webpackChunkName: 'hl-yaml' */ 'highlight.js/lib/languages/yaml'),
+ zephir: () => import(/* webpackChunkName: 'hl-zephir' */ 'highlight.js/lib/languages/zephir'),
+};
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index c2be7bc9195..d665f24bba1 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -48,7 +48,6 @@ import Text from '../extensions/text';
import Video from '../extensions/video';
import WordBreak from '../extensions/word_break';
import {
- isPlainURL,
renderCodeBlock,
renderHardBreak,
renderTable,
@@ -61,36 +60,30 @@ import {
renderPlayable,
renderHTMLNode,
renderContent,
+ preserveUnchanged,
+ bold,
+ italic,
+ link,
+ code,
} from './serialization_helpers';
const defaultSerializerConfig = {
marks: {
- [Bold.name]: defaultMarkdownSerializer.marks.strong,
- [Italic.name]: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true },
- [Code.name]: defaultMarkdownSerializer.marks.code,
+ [Bold.name]: bold,
+ [Italic.name]: italic,
+ [Code.name]: code,
[Subscript.name]: { open: '<sub>', close: '</sub>', mixable: true },
[Superscript.name]: { open: '<sup>', close: '</sup>', mixable: true },
[InlineDiff.name]: {
mixable: true,
- open(state, mark) {
+ open(_, mark) {
return mark.attrs.type === 'addition' ? '{+' : '{-';
},
- close(state, mark) {
+ close(_, mark) {
return mark.attrs.type === 'addition' ? '+}' : '-}';
},
},
- [Link.name]: {
- open(state, mark, parent, index) {
- return isPlainURL(mark, parent, index, 1) ? '<' : '[';
- },
- close(state, mark, parent, index) {
- const href = mark.attrs.canonicalSrc || mark.attrs.href;
-
- return isPlainURL(mark, parent, index, -1)
- ? '>'
- : `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`;
- },
- },
+ [Link.name]: link,
[MathInline.name]: {
open: (...args) => `$${defaultMarkdownSerializer.marks.code.open(...args)}`,
close: (...args) => `${defaultMarkdownSerializer.marks.code.close(...args)}$`,
@@ -119,7 +112,7 @@ const defaultSerializerConfig = {
nodes: {
[Audio.name]: renderPlayable,
- [Blockquote.name]: (state, node) => {
+ [Blockquote.name]: preserveUnchanged((state, node) => {
if (node.attrs.multiline) {
state.write('>>>');
state.ensureNewLine();
@@ -130,9 +123,9 @@ const defaultSerializerConfig = {
} else {
state.wrapBlock('> ', null, node, () => state.renderContent(node));
}
- },
- [BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list,
- [CodeBlockHighlight.name]: renderCodeBlock,
+ }),
+ [BulletList.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.bullet_list),
+ [CodeBlockHighlight.name]: preserveUnchanged(renderCodeBlock),
[Diagram.name]: renderCodeBlock,
[Division.name]: (state, node) => {
if (node.attrs.className?.includes('js-markdown-code')) {
@@ -189,13 +182,13 @@ const defaultSerializerConfig = {
},
[Figure.name]: renderHTMLNode('figure'),
[FigureCaption.name]: renderHTMLNode('figcaption'),
- [HardBreak.name]: renderHardBreak,
- [Heading.name]: defaultMarkdownSerializer.nodes.heading,
- [HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule,
- [Image.name]: renderImage,
- [ListItem.name]: defaultMarkdownSerializer.nodes.list_item,
- [OrderedList.name]: renderOrderedList,
- [Paragraph.name]: defaultMarkdownSerializer.nodes.paragraph,
+ [HardBreak.name]: preserveUnchanged(renderHardBreak),
+ [Heading.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.heading),
+ [HorizontalRule.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.horizontal_rule),
+ [Image.name]: preserveUnchanged(renderImage),
+ [ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
+ [OrderedList.name]: preserveUnchanged(renderOrderedList),
+ [Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph),
[Reference.name]: (state, node) => {
state.write(node.attrs.originalText || node.attrs.text);
},
@@ -221,29 +214,60 @@ const defaultSerializerConfig = {
},
};
+const createChangeTracker = (doc, pristineDoc) => {
+ const changeTracker = new WeakMap();
+ const pristineSourceMarkdownMap = new Map();
+
+ if (doc && pristineDoc) {
+ pristineDoc.descendants((node) => {
+ if (node.attrs.sourceMapKey) {
+ pristineSourceMarkdownMap.set(`${node.attrs.sourceMapKey}${node.type.name}`, node);
+ }
+ });
+ doc.descendants((node) => {
+ const pristineNode = pristineSourceMarkdownMap.get(
+ `${node.attrs.sourceMapKey}${node.type.name}`,
+ );
+
+ if (pristineNode) {
+ changeTracker.set(node, node.eq(pristineNode));
+ }
+ });
+ }
+
+ return changeTracker;
+};
+
/**
- * A markdown serializer converts arbitrary Markdown content
- * into a ProseMirror document and viceversa. To convert Markdown
- * into a ProseMirror document, the Markdown should be rendered.
+ * Converts a ProseMirror document to Markdown. See the
+ * following documentation to learn how to implement
+ * custom node and mark serializer functions.
+ *
+ * https://github.com/prosemirror/prosemirror-markdown
*
- * The client should provide a render function to allow flexibility
- * on the desired rendering approach.
+ * @param {Object} params.nodes ProseMirror node serializer functions
+ * @param {Object} params.marks ProseMirror marks serializer config
*
- * @param {Function} params.render Render function
- * that parses the Markdown and converts it into HTML.
* @returns a markdown serializer
*/
export default ({ serializerConfig = {} } = {}) => ({
/**
- * Converts a ProseMirror JSONDocument based
- * on a ProseMirror schema into Markdown
- * @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines
- * the types of content supported in the document
- * @param {String} params.content A ProseMirror JSONDocument
- * @returns A Markdown string
+ * Serializes a ProseMirror document as Markdown. If a node contains
+ * sourcemap metadata, the serializer is capable of restoring the
+ * Markdown from which the node was generated using a Markdown
+ * deserializer.
+ *
+ * See the Sourcemap metadata extension and the remark_markdown_deserializer
+ * service for more information.
+ *
+ * @param {ProseMirror.Node} params.doc ProseMirror document to convert into Markdown
+ * @param {ProseMirror.Node} params.pristineDoc Pristine version of the document that
+ * should be converted into Markdown. This is used to detect which nodes in the document
+ * changed.
+ * @returns A String that represents the serialized document as Markdown
*/
- serialize: ({ schema, content }) => {
- const proseMirrorDocument = schema.nodeFromJSON(content);
+ serialize: ({ doc, pristineDoc }) => {
+ const changeTracker = createChangeTracker(doc, pristineDoc);
const serializer = new ProseMirrorMarkdownSerializer(
{
...defaultSerializerConfig.nodes,
@@ -255,8 +279,9 @@ export default ({ serializerConfig = {} } = {}) => ({
},
);
- return serializer.serialize(proseMirrorDocument, {
+ return serializer.serialize(doc, {
tightLists: true,
+ changeTracker,
});
},
});
diff --git a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
index 4285e04bbab..fe1b32c5b0a 100644
--- a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
+++ b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
@@ -30,7 +30,7 @@ export const getMarkdownSource = (element) => {
for (let i = range.start.row; i <= range.end.row; i += 1) {
if (i === range.start.row) {
- elSource += source[i]?.substring(range.start.col);
+ elSource += source[i].substring(range.start.col);
} else if (i === range.end.row) {
elSource += `\n${source[i]?.substring(0, range.start.col)}`;
} else {
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
new file mode 100644
index 00000000000..770de1df0d0
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -0,0 +1,87 @@
+import { isString } from 'lodash';
+import { render } from '~/lib/gfm';
+import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter';
+
+const factorySpecs = {
+ blockquote: { block: 'blockquote' },
+ p: { block: 'paragraph' },
+ li: { block: 'listItem', wrapTextInParagraph: true },
+ ul: { block: 'bulletList' },
+ ol: { block: 'orderedList' },
+ h1: {
+ block: 'heading',
+ getAttrs: () => ({ level: 1 }),
+ },
+ h2: {
+ block: 'heading',
+ getAttrs: () => ({ level: 2 }),
+ },
+ h3: {
+ block: 'heading',
+ getAttrs: () => ({ level: 3 }),
+ },
+ h4: {
+ block: 'heading',
+ getAttrs: () => ({ level: 4 }),
+ },
+ h5: {
+ block: 'heading',
+ getAttrs: () => ({ level: 5 }),
+ },
+ h6: {
+ block: 'heading',
+ getAttrs: () => ({ level: 6 }),
+ },
+ pre: {
+ block: 'codeBlock',
+ skipChildren: true,
+ getContent: ({ hastNodeText }) => hastNodeText.replace(/\n$/, ''),
+ getAttrs: (hastNode) => {
+ const languageClass = hastNode.children[0]?.properties.className?.[0];
+ const language = isString(languageClass) ? languageClass.replace('language-', '') : null;
+
+ return { language };
+ },
+ },
+ hr: { inline: 'horizontalRule' },
+ img: {
+ inline: 'image',
+ getAttrs: (hastNode) => ({
+ src: hastNode.properties.src,
+ title: hastNode.properties.title,
+ alt: hastNode.properties.alt,
+ }),
+ },
+ br: { inline: 'hardBreak' },
+ code: { mark: 'code' },
+ em: { mark: 'italic' },
+ i: { mark: 'italic' },
+ strong: { mark: 'bold' },
+ b: { mark: 'bold' },
+ a: {
+ mark: 'link',
+ getAttrs: (hastNode) => ({
+ href: hastNode.properties.href,
+ title: hastNode.properties.title,
+ }),
+ },
+};
+
+export default () => {
+ return {
+ deserialize: async ({ schema, content: markdown }) => {
+ const document = await render({
+ markdown,
+ renderer: (tree) =>
+ createProseMirrorDocFromMdastTree({
+ schema,
+ factorySpecs,
+ tree,
+ source: markdown,
+ }),
+ });
+
+ return { document };
+ },
+ };
+};
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 3e48434c6f9..089d30edec7 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -349,3 +349,134 @@ export function renderCodeBlock(state, node) {
state.write('```');
state.closeBlock(node);
}
+
+export function preserveUnchanged(render) {
+ return (state, node, parent, index) => {
+ const { sourceMarkdown } = node.attrs;
+ const same = state.options.changeTracker.get(node);
+
+ if (same) {
+ state.write(sourceMarkdown);
+ state.closeBlock(node);
+ } else {
+ render(state, node, parent, index);
+ }
+ };
+}
+
+const generateBoldTags = (open = true) => {
+ return (_, mark) => {
+ const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1];
+
+ switch (type) {
+ case '**':
+ case '__':
+ return type;
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ case '<strong':
+ case '<b':
+ return (open ? openTag : closeTag)(type.substring(1));
+ default:
+ return '**';
+ }
+ };
+};
+
+export const bold = {
+ open: generateBoldTags(),
+ close: generateBoldTags(false),
+ mixable: true,
+ expelEnclosingWhitespace: true,
+};
+
+const generateItalicTag = (open = true) => {
+ return (_, mark) => {
+ const type = /^(\*|_|<em|<i).*/.exec(mark.attrs.sourceMarkdown)?.[1];
+
+ switch (type) {
+ case '*':
+ case '_':
+ return type;
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ case '<em':
+ case '<i':
+ return (open ? openTag : closeTag)(type.substring(1));
+ default:
+ return '_';
+ }
+ };
+};
+
+export const italic = {
+ open: generateItalicTag(),
+ close: generateItalicTag(false),
+ mixable: true,
+ expelEnclosingWhitespace: true,
+};
+
+const generateCodeTag = (open = true) => {
+ return (_, mark) => {
+ const type = /^(`|<code).*/.exec(mark.attrs.sourceMarkdown)?.[1];
+
+ if (type === '<code') {
+ return (open ? openTag : closeTag)(type.substring(1));
+ }
+
+ return '`';
+ };
+};
+
+export const code = {
+ open: generateCodeTag(),
+ close: generateCodeTag(false),
+ mixable: true,
+ expelEnclosingWhitespace: true,
+};
+
+const LINK_HTML = 'linkHtml';
+const LINK_MARKDOWN = 'linkMarkdown';
+
+const linkType = (sourceMarkdown) => {
+ const expression = /^(\[|<a).*/.exec(sourceMarkdown)?.[1];
+
+ if (!expression || expression === '[') {
+ return LINK_MARKDOWN;
+ }
+
+ return LINK_HTML;
+};
+
+export const link = {
+ open(state, mark, parent, index) {
+ if (isPlainURL(mark, parent, index, 1)) {
+ return '<';
+ }
+
+ const { canonicalSrc, href, title, sourceMarkdown } = mark.attrs;
+
+ if (linkType(sourceMarkdown) === LINK_MARKDOWN) {
+ return '[';
+ }
+
+ const attrs = { href: state.esc(href || canonicalSrc) };
+
+ if (title) {
+ attrs.title = title;
+ }
+
+ return openTag('a', attrs);
+ },
+ close(state, mark, parent, index) {
+ if (isPlainURL(mark, parent, index, -1)) {
+ return '>';
+ }
+
+ const { canonicalSrc, href, title, sourceMarkdown } = mark.attrs;
+
+ if (linkType(sourceMarkdown) === LINK_HTML) {
+ return closeTag('a');
+ }
+
+ return `](${state.esc(canonicalSrc || href)}${title ? ` ${state.quote(title)}` : ''})`;
+ },
+};
diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js
index ed2c4b39131..09f0738b51b 100644
--- a/app/assets/javascripts/content_editor/services/upload_helpers.js
+++ b/app/assets/javascripts/content_editor/services/upload_helpers.js
@@ -70,6 +70,8 @@ const uploadContent = async ({ type, editor, file, uploadsPath, renderMarkdown,
const position = state.selection.from - 1;
const { tr } = state;
+ editor.commands.setNodeSelection(position);
+
try {
const { src, canonicalSrc } = await uploadFile({ file, uploadsPath, renderMarkdown });
@@ -81,6 +83,8 @@ const uploadContent = async ({ type, editor, file, uploadsPath, renderMarkdown,
canonicalSrc,
}),
);
+
+ editor.commands.setNodeSelection(position);
} catch (e) {
editor.commands.deleteRange({ from: position, to: position + 1 });
eventHub.$emit('alert', {
diff --git a/app/assets/javascripts/content_editor/services/utils.js b/app/assets/javascripts/content_editor/services/utils.js
index b3856b0dd74..e352fa8a9db 100644
--- a/app/assets/javascripts/content_editor/services/utils.js
+++ b/app/assets/javascripts/content_editor/services/utils.js
@@ -15,7 +15,7 @@ export const hasSelection = (tiptapEditor) => {
* @returns {string}
*/
export const extractFilename = (src) => {
- return src.replace(/^.*\/|\..+?$/g, '');
+ return src.replace(/^.*\/|\.[^.]+?$/g, '');
};
export const readFileAsDataURL = (file) => {
diff --git a/app/assets/javascripts/create_cluster/components/cluster_form_dropdown.vue b/app/assets/javascripts/create_cluster/components/cluster_form_dropdown.vue
deleted file mode 100644
index fa0a17f3643..00000000000
--- a/app/assets/javascripts/create_cluster/components/cluster_form_dropdown.vue
+++ /dev/null
@@ -1,246 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import $ from 'jquery';
-import { isNil } from 'lodash';
-import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
-import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
-import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
-
-const toArray = (value) => (isNil(value) ? [] : [].concat(value));
-const itemsProp = (items, prop) => items.map((item) => item[prop]);
-const defaultSearchFn = (searchQuery, labelProp) => (item) =>
- item[labelProp].toLowerCase().indexOf(searchQuery) > -1;
-
-export default {
- components: {
- DropdownButton,
- DropdownSearchInput,
- DropdownHiddenInput,
- GlIcon,
- },
- props: {
- fieldName: {
- type: String,
- required: false,
- default: '',
- },
- placeholder: {
- type: String,
- required: false,
- default: '',
- },
- defaultValue: {
- type: String,
- required: false,
- default: '',
- },
- value: {
- type: [Object, Array, String],
- required: false,
- default: () => null,
- },
- labelProperty: {
- type: String,
- required: false,
- default: 'name',
- },
- valueProperty: {
- type: String,
- required: false,
- default: 'value',
- },
- items: {
- type: Array,
- required: false,
- default: () => [],
- },
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- loadingText: {
- type: String,
- required: false,
- default: '',
- },
- disabledText: {
- type: String,
- required: false,
- default: '',
- },
- hasErrors: {
- type: Boolean,
- required: false,
- default: false,
- },
- multiple: {
- type: Boolean,
- required: false,
- default: false,
- },
- errorMessage: {
- type: String,
- required: false,
- default: '',
- },
- searchFieldPlaceholder: {
- type: String,
- required: false,
- default: '',
- },
- emptyText: {
- type: String,
- required: false,
- default: '',
- },
- searchFn: {
- type: Function,
- required: false,
- default: defaultSearchFn,
- },
- },
- data() {
- return {
- searchQuery: '',
- focusOnSearch: false,
- };
- },
- computed: {
- toggleText() {
- if (this.loading && this.loadingText) {
- return this.loadingText;
- }
-
- if (this.disabled && this.disabledText) {
- return this.disabledText;
- }
-
- if (!this.selectedItems.length) {
- return this.placeholder;
- }
-
- return this.selectedItemsLabels;
- },
- results() {
- return this.getItemsOrEmptyList().filter(this.searchFn(this.searchQuery, this.labelProperty));
- },
- selectedItems() {
- const valueProp = this.valueProperty;
- const valueList = toArray(this.value);
- const items = this.getItemsOrEmptyList();
-
- return items.filter((item) => valueList.some((value) => item[valueProp] === value));
- },
- selectedItemsLabels() {
- return itemsProp(this.selectedItems, this.labelProperty).join(', ');
- },
- selectedItemsValues() {
- return itemsProp(this.selectedItems, this.valueProperty).join(', ');
- },
- },
- mounted() {
- $(this.$refs.dropdown)
- .on('shown.bs.dropdown', () => {
- this.focusOnSearch = true;
- })
- .on('hidden.bs.dropdown', () => {
- this.focusOnSearch = false;
- });
- },
- beforeDestroy() {
- // eslint-disable-next-line @gitlab/no-global-event-off
- $(this.$refs.dropdown).off();
- },
- methods: {
- getItemsOrEmptyList() {
- return this.items || [];
- },
- selectSingle(item) {
- this.$emit('input', item[this.valueProperty]);
- },
- selectMultiple(item) {
- const value = toArray(this.value);
- const itemValue = item[this.valueProperty];
- const itemValueIndex = value.indexOf(itemValue);
-
- if (itemValueIndex > -1) {
- value.splice(itemValueIndex, 1);
- } else {
- value.push(itemValue);
- }
-
- this.$emit('input', value);
- },
- isSelected(item) {
- return this.selectedItems.includes(item);
- },
- },
-};
-</script>
-
-<template>
- <div>
- <div ref="dropdown" class="dropdown">
- <dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
- <dropdown-button
- :class="{ 'border-danger': hasErrors }"
- :is-disabled="disabled"
- :is-loading="loading"
- :toggle-text="toggleText"
- />
- <div class="dropdown-menu dropdown-select">
- <dropdown-search-input
- v-model="searchQuery"
- :focused="focusOnSearch"
- :placeholder-text="searchFieldPlaceholder"
- />
- <div class="dropdown-content">
- <ul>
- <li v-if="!results.length">
- <span class="js-empty-text menu-item">{{ emptyText }}</span>
- </li>
- <li v-for="item in results" :key="item.id">
- <button
- v-if="multiple"
- class="js-dropdown-item d-flex align-items-center"
- type="button"
- @click.stop.prevent="selectMultiple(item)"
- >
- <gl-icon
- :class="[{ invisible: !isSelected(item) }, 'mr-1']"
- name="mobile-issue-close"
- />
- <slot name="item" :item="item">{{ item.name }}</slot>
- </button>
- <button
- v-else
- class="js-dropdown-item"
- type="button"
- @click.prevent="selectSingle(item)"
- >
- <slot name="item" :item="item">{{ item.name }}</slot>
- </button>
- </li>
- </ul>
- </div>
- </div>
- </div>
- <span
- v-if="hasErrors && errorMessage"
- :class="[
- 'form-text js-eks-dropdown-error-message',
- {
- 'text-danger': hasErrors,
- 'text-muted': !hasErrors,
- },
- ]"
- >{{ errorMessage }}</span
- >
- </div>
-</template>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue
deleted file mode 100644
index ba170dc0e19..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<script>
-import { mapState } from 'vuex';
-import EksClusterConfigurationForm from './eks_cluster_configuration_form.vue';
-import ServiceCredentialsForm from './service_credentials_form.vue';
-
-export default {
- components: {
- ServiceCredentialsForm,
- EksClusterConfigurationForm,
- },
- props: {
- gitlabManagedClusterHelpPath: {
- type: String,
- required: true,
- },
- namespacePerEnvironmentHelpPath: {
- type: String,
- required: true,
- },
- kubernetesIntegrationHelpPath: {
- type: String,
- required: true,
- },
- accountAndExternalIdsHelpPath: {
- type: String,
- required: true,
- },
- createRoleArnHelpPath: {
- type: String,
- required: true,
- },
- externalLinkIcon: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState(['hasCredentials']),
- },
-};
-</script>
-<template>
- <div class="js-create-eks-cluster">
- <eks-cluster-configuration-form
- v-if="hasCredentials"
- :gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath"
- :namespace-per-environment-help-path="namespacePerEnvironmentHelpPath"
- :kubernetes-integration-help-path="kubernetesIntegrationHelpPath"
- :external-link-icon="externalLinkIcon"
- />
- <service-credentials-form
- v-else
- :create-role-arn-help-path="createRoleArnHelpPath"
- :account-and-external-ids-help-path="accountAndExternalIdsHelpPath"
- :external-link-icon="externalLinkIcon"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
deleted file mode 100644
index 73458a463f2..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
+++ /dev/null
@@ -1,530 +0,0 @@
-<script>
-import {
- GlFormGroup,
- GlFormInput,
- GlFormCheckbox,
- GlIcon,
- GlLink,
- GlSprintf,
- GlButton,
-} from '@gitlab/ui';
-import { createNamespacedHelpers, mapState, mapActions, mapGetters } from 'vuex';
-import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
-import { s__ } from '~/locale';
-import { KUBERNETES_VERSIONS } from '../constants';
-
-const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles');
-const { mapState: mapKeyPairsState, mapActions: mapKeyPairsActions } = createNamespacedHelpers(
- 'keyPairs',
-);
-const { mapState: mapVpcsState, mapActions: mapVpcActions } = createNamespacedHelpers('vpcs');
-const { mapState: mapSubnetsState, mapActions: mapSubnetActions } = createNamespacedHelpers(
- 'subnets',
-);
-const {
- mapState: mapSecurityGroupsState,
- mapActions: mapSecurityGroupsActions,
-} = createNamespacedHelpers('securityGroups');
-const { mapState: mapInstanceTypesState } = createNamespacedHelpers('instanceTypes');
-
-export default {
- components: {
- ClusterFormDropdown,
- GlFormCheckbox,
- GlFormGroup,
- GlFormInput,
- GlIcon,
- GlLink,
- GlSprintf,
- GlButton,
- },
- props: {
- gitlabManagedClusterHelpPath: {
- type: String,
- required: true,
- },
- namespacePerEnvironmentHelpPath: {
- type: String,
- required: true,
- },
- kubernetesIntegrationHelpPath: {
- type: String,
- required: true,
- },
- externalLinkIcon: {
- type: String,
- required: true,
- },
- },
- i18n: {
- kubernetesIntegrationHelpText: s__(
- 'ClusterIntegration|Read our %{linkStart}help page%{linkEnd} on Kubernetes cluster integration.',
- ),
- roleDropdownHelpText: s__(
- 'ClusterIntegration|Your service role is distinct from the provision role used when authenticating. It will allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role, first create one on %{linkStart}Amazon Web Services%{linkEnd}.',
- ),
- roleDropdownHelpPath:
- 'https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html#create-service-role',
- regionInputLabel: s__('ClusterIntegration|Cluster Region'),
- regionHelpText: s__(
- 'ClusterIntegration|The region the new cluster will be created in. You must reauthenticate to change regions.',
- ),
- keyPairDropdownHelpText: s__(
- 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{linkStart}Amazon Web Services%{linkEnd}.',
- ),
- keyPairDropdownHelpPath:
- 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair',
- vpcDropdownHelpText: s__(
- 'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{linkStart}Amazon Web Services %{linkEnd}.',
- ),
- vpcDropdownHelpPath:
- 'https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#vpc-create',
- subnetDropdownHelpText: s__(
- 'ClusterIntegration|Choose the %{linkStart}subnets %{linkEnd} in your VPC where your worker nodes will run.',
- ),
- subnetDropdownHelpPath: 'https://console.aws.amazon.com/vpc/home?#subnets',
- securityGroupDropdownHelpText: s__(
- 'ClusterIntegration|Choose the %{linkStart}security group%{linkEnd} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
- ),
- securityGroupDropdownHelpPath: 'https://console.aws.amazon.com/vpc/home?#securityGroups',
- instanceTypesDropdownHelpText: s__(
- 'ClusterIntegration|Choose the worker node %{linkStart}instance type%{linkEnd}.',
- ),
- instanceTypesDropdownHelpPath: 'https://aws.amazon.com/ec2/instance-types',
- gitlabManagedClusterHelpText: s__(
- 'ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{linkStart}More information%{linkEnd}',
- ),
- namespacePerEnvironmentHelpText: s__(
- 'ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared. %{linkStart}More information%{linkEnd}',
- ),
- },
- computed: {
- ...mapState([
- 'clusterName',
- 'environmentScope',
- 'kubernetesVersion',
- 'selectedRegion',
- 'selectedKeyPair',
- 'selectedVpc',
- 'selectedSubnet',
- 'selectedRole',
- 'selectedSecurityGroup',
- 'selectedInstanceType',
- 'nodeCount',
- 'gitlabManagedCluster',
- 'namespacePerEnvironment',
- 'isCreatingCluster',
- ]),
- ...mapGetters(['subnetValid']),
- ...mapRolesState({
- roles: 'items',
- isLoadingRoles: 'isLoadingItems',
- loadingRolesError: 'loadingItemsError',
- }),
- ...mapKeyPairsState({
- keyPairs: 'items',
- isLoadingKeyPairs: 'isLoadingItems',
- loadingKeyPairsError: 'loadingItemsError',
- }),
- ...mapVpcsState({
- vpcs: 'items',
- isLoadingVpcs: 'isLoadingItems',
- loadingVpcsError: 'loadingItemsError',
- }),
- ...mapSubnetsState({
- subnets: 'items',
- isLoadingSubnets: 'isLoadingItems',
- loadingSubnetsError: 'loadingItemsError',
- }),
- ...mapSecurityGroupsState({
- securityGroups: 'items',
- isLoadingSecurityGroups: 'isLoadingItems',
- loadingSecurityGroupsError: 'loadingItemsError',
- }),
- ...mapInstanceTypesState({
- instanceTypes: 'items',
- isLoadingInstanceTypes: 'isLoadingItems',
- loadingInstanceTypesError: 'loadingItemsError',
- }),
- kubernetesVersions() {
- return KUBERNETES_VERSIONS;
- },
- vpcDropdownDisabled() {
- return !this.selectedRegion;
- },
- keyPairDropdownDisabled() {
- return !this.selectedRegion;
- },
- subnetDropdownDisabled() {
- return !this.selectedVpc;
- },
- securityGroupDropdownDisabled() {
- return !this.selectedVpc;
- },
- createClusterButtonDisabled() {
- return (
- !this.clusterName ||
- !this.environmentScope ||
- !this.kubernetesVersion ||
- !this.selectedRegion ||
- !this.selectedKeyPair ||
- !this.selectedVpc ||
- !this.subnetValid ||
- !this.selectedRole ||
- !this.selectedSecurityGroup ||
- !this.selectedInstanceType ||
- !this.nodeCount ||
- this.isCreatingCluster
- );
- },
- displaySubnetError() {
- return Boolean(this.loadingSubnetsError) || this.selectedSubnet?.length === 1;
- },
- createClusterButtonLabel() {
- return this.isCreatingCluster
- ? s__('ClusterIntegration|Creating Kubernetes cluster')
- : s__('ClusterIntegration|Create Kubernetes cluster');
- },
- subnetValidationErrorText() {
- if (this.loadingSubnetsError) {
- return s__('ClusterIntegration|Could not load subnets for the selected VPC');
- }
-
- return s__('ClusterIntegration|You should select at least two subnets');
- },
- },
- mounted() {
- this.fetchRoles();
- this.setRegionAndFetchVpcsAndKeyPairs();
- },
- methods: {
- ...mapActions([
- 'createCluster',
- 'setClusterName',
- 'setEnvironmentScope',
- 'setKubernetesVersion',
- 'setRegion',
- 'setVpc',
- 'setSubnet',
- 'setRole',
- 'setKeyPair',
- 'setSecurityGroup',
- 'setInstanceType',
- 'setNodeCount',
- 'setGitlabManagedCluster',
- 'setNamespacePerEnvironment',
- ]),
- ...mapVpcActions({ fetchVpcs: 'fetchItems' }),
- ...mapSubnetActions({ fetchSubnets: 'fetchItems' }),
- ...mapRolesActions({ fetchRoles: 'fetchItems' }),
- ...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
- ...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
- setRegionAndFetchVpcsAndKeyPairs() {
- this.setVpc({ vpc: null });
- this.setKeyPair({ keyPair: null });
- this.setSubnet({ subnet: [] });
- this.setSecurityGroup({ securityGroup: null });
- this.fetchVpcs({ region: this.selectedRegion });
- this.fetchKeyPairs({ region: this.selectedRegion });
- },
- setVpcAndFetchSubnets(vpc) {
- this.setVpc({ vpc });
- this.setSubnet({ subnet: [] });
- this.setSecurityGroup({ securityGroup: null });
- this.fetchSubnets({ vpc, region: this.selectedRegion });
- this.fetchSecurityGroups({ vpc, region: this.selectedRegion });
- },
- },
-};
-</script>
-<template>
- <form name="eks-cluster-configuration-form">
- <h4>
- {{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
- </h4>
- <div class="mb-3">
- <gl-sprintf :message="$options.i18n.kubernetesIntegrationHelpText">
- <template #link="{ content }">
- <gl-link :href="kubernetesIntegrationHelpPath">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-cluster-name">{{
- s__('ClusterIntegration|Kubernetes cluster name')
- }}</label>
- <gl-form-input
- id="eks-cluster-name"
- :value="clusterName"
- @input="setClusterName({ clusterName: $event })"
- />
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-environment-scope">{{
- s__('ClusterIntegration|Environment scope')
- }}</label>
- <gl-form-input
- id="eks-environment-scope"
- :value="environmentScope"
- @input="setEnvironmentScope({ environmentScope: $event })"
- />
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-kubernetes-version">{{
- s__('ClusterIntegration|Kubernetes version')
- }}</label>
- <cluster-form-dropdown
- field-id="eks-kubernetes-version"
- field-name="eks-kubernetes-version"
- :value="kubernetesVersion"
- :items="kubernetesVersions"
- :empty-text="s__('ClusterIntegration|Kubernetes version not found')"
- @input="setKubernetesVersion({ kubernetesVersion: $event })"
- />
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Service role') }}</label>
- <cluster-form-dropdown
- field-id="eks-role"
- field-name="eks-role"
- :value="selectedRole"
- :items="roles"
- :loading="isLoadingRoles"
- :loading-text="s__('ClusterIntegration|Loading IAM Roles')"
- :placeholder="s__('ClusterIntegration|Select service role')"
- :search-field-placeholder="s__('ClusterIntegration|Search IAM Roles')"
- :empty-text="s__('ClusterIntegration|No IAM Roles found')"
- :has-errors="Boolean(loadingRolesError)"
- :error-message="s__('ClusterIntegration|Could not load IAM roles')"
- @input="setRole({ role: $event })"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.roleDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.roleDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <gl-form-group
- :label="$options.i18n.regionInputLabel"
- :description="$options.i18n.regionHelpText"
- >
- <gl-form-input id="eks-region" :value="selectedRegion" type="text" readonly />
- </gl-form-group>
- <div class="form-group">
- <label class="label-bold" for="eks-key-pair">{{
- s__('ClusterIntegration|Key pair name')
- }}</label>
- <cluster-form-dropdown
- field-id="eks-key-pair"
- field-name="eks-key-pair"
- :value="selectedKeyPair"
- :items="keyPairs"
- :disabled="keyPairDropdownDisabled"
- :disabled-text="s__('ClusterIntegration|Select a region to choose a Key Pair')"
- :loading="isLoadingKeyPairs"
- :loading-text="s__('ClusterIntegration|Loading Key Pairs')"
- :placeholder="s__('ClusterIntegration|Select key pair')"
- :search-field-placeholder="s__('ClusterIntegration|Search Key Pairs')"
- :empty-text="s__('ClusterIntegration|No Key Pairs found')"
- :has-errors="Boolean(loadingKeyPairsError)"
- :error-message="s__('ClusterIntegration|Could not load Key Pairs')"
- @input="setKeyPair({ keyPair: $event })"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.keyPairDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.keyPairDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-vpc">{{ s__('ClusterIntegration|VPC') }}</label>
- <cluster-form-dropdown
- field-id="eks-vpc"
- field-name="eks-vpc"
- :value="selectedVpc"
- :items="vpcs"
- :loading="isLoadingVpcs"
- :disabled="vpcDropdownDisabled"
- :disabled-text="s__('ClusterIntegration|Select a region to choose a VPC')"
- :loading-text="s__('ClusterIntegration|Loading VPCs')"
- :placeholder="s__('ClusterIntegration|Select a VPC')"
- :search-field-placeholder="s__('ClusterIntegration|Search VPCs')"
- :empty-text="s__('ClusterIntegration|No VPCs found')"
- :has-errors="Boolean(loadingVpcsError)"
- :error-message="s__('ClusterIntegration|Could not load VPCs for the selected region')"
- @input="setVpcAndFetchSubnets($event)"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.vpcDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.vpcDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnets') }}</label>
- <cluster-form-dropdown
- field-id="eks-subnet"
- field-name="eks-subnet"
- multiple
- :value="selectedSubnet"
- :items="subnets"
- :loading="isLoadingSubnets"
- :disabled="subnetDropdownDisabled"
- :disabled-text="s__('ClusterIntegration|Select a VPC to choose a subnet')"
- :loading-text="s__('ClusterIntegration|Loading subnets')"
- :placeholder="s__('ClusterIntegration|Select a subnet')"
- :search-field-placeholder="s__('ClusterIntegration|Search subnets')"
- :empty-text="s__('ClusterIntegration|No subnet found')"
- :has-errors="displaySubnetError"
- :error-message="subnetValidationErrorText"
- @input="setSubnet({ subnet: $event })"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.subnetDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.subnetDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-security-group">{{
- s__('ClusterIntegration|Security group')
- }}</label>
- <cluster-form-dropdown
- field-id="eks-security-group"
- field-name="eks-security-group"
- :value="selectedSecurityGroup"
- :items="securityGroups"
- :loading="isLoadingSecurityGroups"
- :disabled="securityGroupDropdownDisabled"
- :disabled-text="s__('ClusterIntegration|Select a VPC to choose a security group')"
- :loading-text="s__('ClusterIntegration|Loading security groups')"
- :placeholder="s__('ClusterIntegration|Select a security group')"
- :search-field-placeholder="s__('ClusterIntegration|Search security groups')"
- :empty-text="s__('ClusterIntegration|No security group found')"
- :has-errors="Boolean(loadingSecurityGroupsError)"
- :error-message="
- s__('ClusterIntegration|Could not load security groups for the selected VPC')
- "
- @input="setSecurityGroup({ securityGroup: $event })"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.securityGroupDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.securityGroupDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-instance-type">{{
- s__('ClusterIntegration|Instance type')
- }}</label>
- <cluster-form-dropdown
- field-id="eks-instance-type"
- field-name="eks-instance-type"
- :value="selectedInstanceType"
- :items="instanceTypes"
- :loading="isLoadingInstanceTypes"
- :loading-text="s__('ClusterIntegration|Loading instance types')"
- :placeholder="s__('ClusterIntegration|Select an instance type')"
- :search-field-placeholder="s__('ClusterIntegration|Search instance types')"
- :empty-text="s__('ClusterIntegration|No instance type found')"
- :has-errors="Boolean(loadingInstanceTypesError)"
- :error-message="s__('ClusterIntegration|Could not load instance types')"
- @input="setInstanceType({ instanceType: $event })"
- />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.instanceTypesDropdownHelpText">
- <template #link="{ content }">
- <gl-link :href="$options.i18n.instanceTypesDropdownHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <label class="label-bold" for="eks-node-count">{{
- s__('ClusterIntegration|Number of nodes')
- }}</label>
- <gl-form-input
- id="eks-node-count"
- type="number"
- min="1"
- step="1"
- :value="nodeCount"
- @input="setNodeCount({ nodeCount: $event })"
- />
- </div>
- <div class="form-group">
- <gl-form-checkbox
- :checked="gitlabManagedCluster"
- @input="setGitlabManagedCluster({ gitlabManagedCluster: $event })"
- >{{ s__('ClusterIntegration|GitLab-managed cluster') }}</gl-form-checkbox
- >
- <p class="form text text-muted">
- <gl-sprintf :message="$options.i18n.gitlabManagedClusterHelpText">
- <template #link="{ content }">
- <gl-link :href="gitlabManagedClusterHelpPath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <gl-form-checkbox
- :checked="namespacePerEnvironment"
- @input="setNamespacePerEnvironment({ namespacePerEnvironment: $event })"
- >{{ s__('ClusterIntegration|Namespace per environment') }}</gl-form-checkbox
- >
- <p class="form text text-muted">
- <gl-sprintf :message="$options.i18n.namespacePerEnvironmentHelpText">
- <template #link="{ content }">
- <gl-link :href="namespacePerEnvironmentHelpPath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- <div class="form-group">
- <gl-button
- variant="success"
- category="primary"
- class="js-create-cluster"
- :disabled="createClusterButtonDisabled"
- :loading="isCreatingCluster"
- @click="createCluster()"
- >
- {{ createClusterButtonLabel }}
- </gl-button>
- </div>
- </form>
-</template>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
deleted file mode 100644
index 004c2e26c4e..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
+++ /dev/null
@@ -1,182 +0,0 @@
-<script>
-import { GlButton, GlFormGroup, GlFormInput, GlIcon, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
-import { mapState, mapActions } from 'vuex';
-import { s__, __ } from '~/locale';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { DEFAULT_REGION } from '../constants';
-
-export default {
- components: {
- GlButton,
- GlFormGroup,
- GlFormInput,
- GlIcon,
- GlLink,
- GlSprintf,
- ClipboardButton,
- GlAlert,
- },
- props: {
- accountAndExternalIdsHelpPath: {
- type: String,
- required: true,
- },
- createRoleArnHelpPath: {
- type: String,
- required: true,
- },
- externalLinkIcon: {
- type: String,
- required: true,
- },
- },
- i18n: {
- regionInputLabel: s__('ClusterIntegration|Cluster Region'),
- regionHelpPath: 'https://aws.amazon.com/about-aws/global-infrastructure/regions_az/',
- regionHelpText: s__(
- 'ClusterIntegration|Select the region you want to create the new cluster in. Make sure you have access to this region for your role to be able to authenticate. If no region is selected, we will use %{codeStart}DEFAULT_REGION%{codeEnd}. Learn more about %{linkStart}Regions%{linkEnd}.',
- ),
- accountAndExternalIdsHelpText: s__(
- 'ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provisioned role, first create one on %{awsLinkStart}Amazon Web Services %{awsLinkEnd} using the above account and external IDs. %{moreInfoStart}More information%{moreInfoEnd}',
- ),
- regionHelpTextDefaultRegion: DEFAULT_REGION,
- },
- data() {
- return {
- roleArn: this.$store.state.roleArn,
- selectedRegion: this.$store.state.selectedRegion,
- };
- },
- computed: {
- ...mapState(['accountId', 'externalId', 'isCreatingRole', 'createRoleError']),
- submitButtonDisabled() {
- return this.isCreatingRole || !this.roleArn;
- },
- submitButtonLabel() {
- return this.isCreatingRole
- ? __('Authenticating')
- : s__('ClusterIntegration|Authenticate with AWS');
- },
- awsHelpLink() {
- return 'https://console.aws.amazon.com/iam/home?#roles';
- },
- },
- methods: {
- ...mapActions(['createRole']),
- },
-};
-</script>
-<template>
- <form name="service-credentials-form">
- <h4>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h4>
- <p>
- {{
- s__(
- 'ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN.',
- )
- }}
- </p>
- <gl-alert
- v-if="createRoleError"
- class="js-invalid-credentials gl-mb-5"
- variant="danger"
- :dismissible="false"
- >
- {{ createRoleError }}
- </gl-alert>
- <div class="form-row">
- <div class="form-group col-md-6">
- <label for="gitlab-account-id">{{ __('Account ID') }}</label>
- <div class="input-group">
- <gl-form-input id="gitlab-account-id" type="text" readonly :value="accountId" />
- <div class="input-group-append">
- <clipboard-button
- :text="accountId"
- :title="__('Copy Account ID to clipboard')"
- class="input-group-text js-copy-account-id-button"
- />
- </div>
- </div>
- </div>
- <div class="form-group col-md-6">
- <label for="eks-external-id">{{ __('External ID') }}</label>
- <div class="input-group">
- <gl-form-input id="eks-external-id" type="text" readonly :value="externalId" />
- <div class="input-group-append">
- <clipboard-button
- :text="externalId"
- :title="__('Copy External ID to clipboard')"
- class="input-group-text js-copy-external-id-button"
- />
- </div>
- </div>
- </div>
- <div class="col-12 mb-3 mt-n3">
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.accountAndExternalIdsHelpText">
- <template #awsLink="{ content }">
- <gl-link :href="awsHelpLink" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- <template #moreInfo="{ content }">
- <gl-link :href="accountAndExternalIdsHelpPath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
- </div>
- <div class="form-group">
- <label for="eks-provision-role-arn">{{ s__('ClusterIntegration|Provision Role ARN') }}</label>
- <gl-form-input id="eks-provision-role-arn" v-model="roleArn" />
- <p class="form-text text-muted">
- <gl-sprintf :message="$options.i18n.accountAndExternalIdsHelpText">
- <template #awsLink="{ content }">
- <gl-link :href="awsHelpLink" target="_blank">
- {{ content }}
- <gl-icon name="external-link" class="gl-vertical-align-middle" />
- </gl-link>
- </template>
- <template #moreInfo="{ content }">
- <gl-link :href="accountAndExternalIdsHelpPath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
- </div>
-
- <gl-form-group :label="$options.i18n.regionInputLabel">
- <gl-form-input id="eks-region" v-model="selectedRegion" type="text" />
-
- <template #description>
- <gl-sprintf :message="$options.i18n.regionHelpText">
- <template #code>
- <code>{{ $options.i18n.regionHelpTextDefaultRegion }}</code>
- </template>
-
- <template #link="{ content }">
- <gl-link :href="$options.i18n.regionHelpPath" target="_blank">
- {{ content }}
- <gl-icon name="external-link" />
- </gl-link>
- </template>
- </gl-sprintf>
- </template>
- </gl-form-group>
-
- <gl-button
- variant="success"
- category="primary"
- type="submit"
- :disabled="submitButtonDisabled"
- :loading="isCreatingRole"
- @click.prevent="createRole({ roleArn, selectedRegion, externalId })"
- >
- {{ submitButtonLabel }}
- </gl-button>
- </form>
-</template>
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/constants.js b/app/assets/javascripts/create_cluster/eks_cluster/constants.js
deleted file mode 100644
index 3ed0f050301..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/constants.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export const DEFAULT_REGION = 'us-east-2';
-
-export const KUBERNETES_VERSIONS = [
- { name: '1.16', value: '1.16' },
- { name: '1.17', value: '1.17' },
- { name: '1.18', value: '1.18' },
- { name: '1.19', value: '1.19' },
- { name: '1.20', value: '1.20', default: true },
-];
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/index.js b/app/assets/javascripts/create_cluster/eks_cluster/index.js
deleted file mode 100644
index 38b7eefd15b..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import CreateEksCluster from './components/create_eks_cluster.vue';
-import createStore from './store';
-
-Vue.use(Vuex);
-
-export default (el) => {
- const {
- gitlabManagedClusterHelpPath,
- namespacePerEnvironmentHelpPath,
- kubernetesIntegrationHelpPath,
- accountAndExternalIdsHelpPath,
- createRoleArnHelpPath,
- externalId,
- accountId,
- instanceTypes,
- hasCredentials,
- createRolePath,
- createClusterPath,
- externalLinkIcon,
- roleArn,
- } = el.dataset;
-
- return new Vue({
- el,
- store: createStore({
- initialState: {
- hasCredentials: parseBoolean(hasCredentials),
- externalId,
- accountId,
- instanceTypes: JSON.parse(instanceTypes),
- createRolePath,
- createClusterPath,
- roleArn,
- },
- }),
- components: {
- CreateEksCluster,
- },
- render(createElement) {
- return createElement('create-eks-cluster', {
- props: {
- gitlabManagedClusterHelpPath,
- namespacePerEnvironmentHelpPath,
- kubernetesIntegrationHelpPath,
- accountAndExternalIdsHelpPath,
- createRoleArnHelpPath,
- externalLinkIcon,
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
deleted file mode 100644
index bd9554521b8..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import EC2 from 'aws-sdk/clients/ec2';
-import IAM from 'aws-sdk/clients/iam';
-import AWS from 'aws-sdk/global';
-
-const lookupVpcName = ({ Tags: tags, VpcId: id }) => {
- const nameTag = tags.find(({ Key: key }) => key === 'Name');
-
- return nameTag ? nameTag.Value : id;
-};
-
-export const setAWSConfig = ({ awsCredentials }) => {
- AWS.config = awsCredentials;
-};
-
-export const fetchRoles = () => {
- const iam = new IAM();
-
- return iam
- .listRoles()
- .promise()
- .then(({ Roles: roles }) => roles.map(({ RoleName: name, Arn: value }) => ({ name, value })));
-};
-
-export const fetchKeyPairs = ({ region }) => {
- const ec2 = new EC2({ region });
-
- return ec2
- .describeKeyPairs()
- .promise()
- .then(({ KeyPairs: keyPairs }) => keyPairs.map(({ KeyName: name }) => ({ name, value: name })));
-};
-
-export const fetchVpcs = ({ region }) => {
- const ec2 = new EC2({ region });
-
- return ec2
- .describeVpcs()
- .promise()
- .then(({ Vpcs: vpcs }) =>
- vpcs.map((vpc) => ({
- value: vpc.VpcId,
- name: lookupVpcName(vpc),
- })),
- );
-};
-
-export const fetchSubnets = ({ vpc, region }) => {
- const ec2 = new EC2({ region });
-
- return ec2
- .describeSubnets({
- Filters: [
- {
- Name: 'vpc-id',
- Values: [vpc],
- },
- ],
- })
- .promise()
- .then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ value: id, name: id })));
-};
-
-export const fetchSecurityGroups = ({ region, vpc }) => {
- const ec2 = new EC2({ region });
-
- return ec2
- .describeSecurityGroups({
- Filters: [
- {
- Name: 'vpc-id',
- Values: [vpc],
- },
- ],
- })
- .promise()
- .then(({ SecurityGroups: securityGroups }) =>
- securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
- );
-};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
deleted file mode 100644
index cd8212a40f9..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import createFlash from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { DEFAULT_REGION } from '../constants';
-import { setAWSConfig } from '../services/aws_services_facade';
-import * as types from './mutation_types';
-
-const getErrorMessage = (data) => {
- const errorKey = Object.keys(data)[0];
-
- return data[errorKey][0];
-};
-
-export const setClusterName = ({ commit }, payload) => {
- commit(types.SET_CLUSTER_NAME, payload);
-};
-
-export const setEnvironmentScope = ({ commit }, payload) => {
- commit(types.SET_ENVIRONMENT_SCOPE, payload);
-};
-
-export const setKubernetesVersion = ({ commit }, payload) => {
- commit(types.SET_KUBERNETES_VERSION, payload);
-};
-
-export const createRole = ({ dispatch, state: { createRolePath } }, payload) => {
- dispatch('requestCreateRole');
-
- const region = payload.selectedRegion || DEFAULT_REGION;
-
- return axios
- .post(createRolePath, {
- role_arn: payload.roleArn,
- role_external_id: payload.externalId,
- region,
- })
- .then(({ data }) => {
- const awsData = {
- ...convertObjectPropsToCamelCase(data),
- region,
- };
-
- dispatch('createRoleSuccess', awsData);
- })
- .catch((error) => {
- let message = error;
- if (error?.response?.data?.message) {
- message = error.response.data.message;
- }
- dispatch('createRoleError', { error: message });
- });
-};
-
-export const requestCreateRole = ({ commit }) => {
- commit(types.REQUEST_CREATE_ROLE);
-};
-
-export const createRoleSuccess = ({ dispatch, commit }, awsCredentials) => {
- dispatch('setRegion', { region: awsCredentials.region });
- setAWSConfig({ awsCredentials });
- commit(types.CREATE_ROLE_SUCCESS);
-};
-
-export const createRoleError = ({ commit }, payload) => {
- commit(types.CREATE_ROLE_ERROR, payload);
-};
-
-export const createCluster = ({ dispatch, state }) => {
- dispatch('requestCreateCluster');
-
- return axios
- .post(state.createClusterPath, {
- name: state.clusterName,
- environment_scope: state.environmentScope,
- managed: state.gitlabManagedCluster,
- namespace_per_environment: state.namespacePerEnvironment,
- provider_aws_attributes: {
- kubernetes_version: state.kubernetesVersion,
- region: state.selectedRegion,
- vpc_id: state.selectedVpc,
- subnet_ids: state.selectedSubnet,
- role_arn: state.selectedRole,
- key_name: state.selectedKeyPair,
- security_group_id: state.selectedSecurityGroup,
- instance_type: state.selectedInstanceType,
- num_nodes: state.nodeCount,
- },
- })
- .then(({ headers: { location } }) => dispatch('createClusterSuccess', location))
- .catch(({ response: { data } }) => {
- dispatch('createClusterError', data);
- });
-};
-
-export const requestCreateCluster = ({ commit }) => {
- commit(types.REQUEST_CREATE_CLUSTER);
-};
-
-export const createClusterSuccess = (_, location) => {
- window.location.assign(location);
-};
-
-export const createClusterError = ({ commit }, error) => {
- commit(types.CREATE_CLUSTER_ERROR, error);
- createFlash({
- message: getErrorMessage(error),
- });
-};
-
-export const setRegion = ({ commit }, payload) => {
- commit(types.SET_REGION, payload);
-};
-
-export const setKeyPair = ({ commit }, payload) => {
- commit(types.SET_KEY_PAIR, payload);
-};
-
-export const setVpc = ({ commit }, payload) => {
- commit(types.SET_VPC, payload);
-};
-
-export const setSubnet = ({ commit }, payload) => {
- commit(types.SET_SUBNET, payload);
-};
-
-export const setRole = ({ commit }, payload) => {
- commit(types.SET_ROLE, payload);
-};
-
-export const setSecurityGroup = ({ commit }, payload) => {
- commit(types.SET_SECURITY_GROUP, payload);
-};
-
-export const setGitlabManagedCluster = ({ commit }, payload) => {
- commit(types.SET_GITLAB_MANAGED_CLUSTER, payload);
-};
-
-export const setNamespacePerEnvironment = ({ commit }, payload) => {
- commit(types.SET_NAMESPACE_PER_ENVIRONMENT, payload);
-};
-
-export const setInstanceType = ({ commit }, payload) => {
- commit(types.SET_INSTANCE_TYPE, payload);
-};
-
-export const setNodeCount = ({ commit }, payload) => {
- commit(types.SET_NODE_COUNT, payload);
-};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/getters.js b/app/assets/javascripts/create_cluster/eks_cluster/store/getters.js
deleted file mode 100644
index d8489ca31cf..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/getters.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export const subnetValid = ({ selectedSubnet }) =>
- Array.isArray(selectedSubnet) && selectedSubnet.length >= 2;
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
deleted file mode 100644
index ed054989771..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vuex from 'vuex';
-import clusterDropdownStore from '~/create_cluster/store/cluster_dropdown';
-import {
- fetchRoles,
- fetchKeyPairs,
- fetchVpcs,
- fetchSubnets,
- fetchSecurityGroups,
-} from '../services/aws_services_facade';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import state from './state';
-
-const createStore = ({ initialState }) =>
- new Vuex.Store({
- actions,
- getters,
- mutations,
- state: Object.assign(state(), initialState),
- modules: {
- roles: {
- namespaced: true,
- ...clusterDropdownStore({ fetchFn: fetchRoles }),
- },
- keyPairs: {
- namespaced: true,
- ...clusterDropdownStore({ fetchFn: fetchKeyPairs }),
- },
- vpcs: {
- namespaced: true,
- ...clusterDropdownStore({ fetchFn: fetchVpcs }),
- },
- subnets: {
- namespaced: true,
- ...clusterDropdownStore({ fetchFn: fetchSubnets }),
- },
- securityGroups: {
- namespaced: true,
- ...clusterDropdownStore({ fetchFn: fetchSecurityGroups }),
- },
- instanceTypes: {
- namespaced: true,
- ...clusterDropdownStore({ initialState: { items: initialState.instanceTypes } }),
- },
- },
- });
-
-export default createStore;
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
deleted file mode 100644
index 4a48195a27b..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
+++ /dev/null
@@ -1,19 +0,0 @@
-export const SET_CLUSTER_NAME = 'SET_CLUSTER_NAME';
-export const SET_ENVIRONMENT_SCOPE = 'SET_ENVIRONMENT_SCOPE';
-export const SET_KUBERNETES_VERSION = 'SET_KUBERNETES_VERSION';
-export const SET_REGION = 'SET_REGION';
-export const SET_VPC = 'SET_VPC';
-export const SET_KEY_PAIR = 'SET_KEY_PAIR';
-export const SET_SUBNET = 'SET_SUBNET';
-export const SET_ROLE = 'SET_ROLE';
-export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP';
-export const SET_INSTANCE_TYPE = 'SET_INSTANCE_TYPE';
-export const SET_NODE_COUNT = 'SET_NODE_COUNT';
-export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
-export const SET_NAMESPACE_PER_ENVIRONMENT = 'SET_NAMESPACE_PER_ENVIRONMENT';
-export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
-export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
-export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR';
-export const REQUEST_CREATE_CLUSTER = 'REQUEST_CREATE_CLUSTER';
-export const CREATE_CLUSTER_SUCCESS = 'CREATE_CLUSTER_SUCCESS';
-export const CREATE_CLUSTER_ERROR = 'CREATE_CLUSTER_ERROR';
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
deleted file mode 100644
index f57236e0e31..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as types from './mutation_types';
-
-export default {
- [types.SET_CLUSTER_NAME](state, { clusterName }) {
- state.clusterName = clusterName;
- },
- [types.SET_ENVIRONMENT_SCOPE](state, { environmentScope }) {
- state.environmentScope = environmentScope;
- },
- [types.SET_KUBERNETES_VERSION](state, { kubernetesVersion }) {
- state.kubernetesVersion = kubernetesVersion;
- },
- [types.SET_REGION](state, { region }) {
- state.selectedRegion = region;
- },
- [types.SET_KEY_PAIR](state, { keyPair }) {
- state.selectedKeyPair = keyPair;
- },
- [types.SET_VPC](state, { vpc }) {
- state.selectedVpc = vpc;
- },
- [types.SET_SUBNET](state, { subnet }) {
- state.selectedSubnet = subnet;
- },
- [types.SET_ROLE](state, { role }) {
- state.selectedRole = role;
- },
- [types.SET_SECURITY_GROUP](state, { securityGroup }) {
- state.selectedSecurityGroup = securityGroup;
- },
- [types.SET_INSTANCE_TYPE](state, { instanceType }) {
- state.selectedInstanceType = instanceType;
- },
- [types.SET_NODE_COUNT](state, { nodeCount }) {
- state.nodeCount = nodeCount;
- },
- [types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) {
- state.gitlabManagedCluster = gitlabManagedCluster;
- },
- [types.SET_NAMESPACE_PER_ENVIRONMENT](state, { namespacePerEnvironment }) {
- state.namespacePerEnvironment = namespacePerEnvironment;
- },
- [types.REQUEST_CREATE_ROLE](state) {
- state.isCreatingRole = true;
- state.createRoleError = null;
- state.hasCredentials = false;
- },
- [types.CREATE_ROLE_SUCCESS](state) {
- state.isCreatingRole = false;
- state.createRoleError = null;
- state.hasCredentials = true;
- },
- [types.CREATE_ROLE_ERROR](state, { error }) {
- state.isCreatingRole = false;
- state.createRoleError = error;
- state.hasCredentials = false;
- },
- [types.REQUEST_CREATE_CLUSTER](state) {
- state.isCreatingCluster = true;
- state.createClusterError = null;
- },
- [types.CREATE_CLUSTER_ERROR](state, { error }) {
- state.isCreatingCluster = false;
- state.createClusterError = error;
- },
-};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
deleted file mode 100644
index c906ddf9011..00000000000
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { KUBERNETES_VERSIONS } from '../constants';
-
-const kubernetesVersion = KUBERNETES_VERSIONS.find((version) => version.default).value;
-
-export default () => ({
- createRolePath: null,
-
- isCreatingRole: false,
- roleCreated: false,
- createRoleError: false,
-
- accountId: '',
- externalId: '',
-
- roleArn: '',
-
- clusterName: '',
- environmentScope: '*',
- kubernetesVersion,
- selectedRegion: '',
- selectedRole: '',
- selectedKeyPair: '',
- selectedVpc: '',
- selectedSubnet: [],
- selectedSecurityGroup: '',
- selectedInstanceType: 'm5.large',
- nodeCount: '3',
-
- isCreatingCluster: false,
- createClusterError: false,
-
- gitlabManagedCluster: true,
- namespacePerEnvironment: true,
-});
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js
deleted file mode 100644
index 1246fdb19d7..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
-import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
-import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
-
-import store from '../store';
-
-export default {
- store,
- components: {
- DropdownButton,
- DropdownSearchInput,
- DropdownHiddenInput,
- GlLoadingIcon,
- },
- props: {
- fieldId: {
- type: String,
- required: true,
- },
- fieldName: {
- type: String,
- required: true,
- },
- defaultValue: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- isLoading: false,
- hasErrors: false,
- searchQuery: '',
- gapiError: '',
- };
- },
- computed: {
- results() {
- if (!this.items) {
- return [];
- }
-
- return this.items.filter((item) => item.name.toLowerCase().indexOf(this.searchQuery) > -1);
- },
- },
- methods: {
- fetchSuccessHandler() {
- if (this.defaultValue) {
- const itemToSelect = this.items.find((item) => item.name === this.defaultValue);
-
- if (itemToSelect) {
- this.setItem(itemToSelect.name);
- }
- }
-
- this.isLoading = false;
- this.hasErrors = false;
- },
- fetchFailureHandler(resp) {
- this.isLoading = false;
- this.hasErrors = true;
-
- if (resp.result && resp.result.error) {
- this.gapiError = resp.result.error.message;
- }
- },
- },
-};
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue
deleted file mode 100644
index 23c477bfbfd..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script>
-import { mapState, mapGetters, mapActions } from 'vuex';
-import { sprintf, s__ } from '~/locale';
-
-import gkeDropdownMixin from './gke_dropdown_mixin';
-
-export default {
- name: 'GkeMachineTypeDropdown',
- mixins: [gkeDropdownMixin],
- computed: {
- ...mapState([
- 'isValidatingProjectBilling',
- 'projectHasBillingEnabled',
- 'selectedZone',
- 'selectedMachineType',
- ]),
- ...mapState({ items: 'machineTypes' }),
- ...mapGetters(['hasZone', 'hasMachineType']),
- isDisabled() {
- return (
- this.isLoading ||
- this.isValidatingProjectBilling ||
- !this.projectHasBillingEnabled ||
- !this.hasZone
- );
- },
- toggleText() {
- if (this.isLoading) {
- return s__('ClusterIntegration|Fetching machine types');
- }
-
- if (this.selectedMachineType) {
- return this.selectedMachineType;
- }
-
- if (!this.projectHasBillingEnabled && !this.hasZone) {
- return s__('ClusterIntegration|Select project and zone to choose machine type');
- }
-
- return !this.hasZone
- ? s__('ClusterIntegration|Select zone to choose machine type')
- : s__('ClusterIntegration|Select machine type');
- },
- errorMessage() {
- return sprintf(
- s__(
- 'ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}',
- ),
- { error: this.gapiError },
- );
- },
- },
- watch: {
- selectedZone() {
- this.hasErrors = false;
-
- if (this.hasZone) {
- this.isLoading = true;
-
- this.fetchMachineTypes().then(this.fetchSuccessHandler).catch(this.fetchFailureHandler);
- }
- },
- },
- methods: {
- ...mapActions(['fetchMachineTypes']),
- ...mapActions({ setItem: 'setMachineType' }),
- },
-};
-</script>
-
-<template>
- <div>
- <div class="js-gcp-machine-type-dropdown dropdown">
- <dropdown-hidden-input :name="fieldName" :value="selectedMachineType" />
- <dropdown-button
- :class="{ 'border-danger': hasErrors }"
- :is-disabled="isDisabled"
- :is-loading="isLoading"
- :toggle-text="toggleText"
- />
- <div class="dropdown-menu dropdown-select">
- <dropdown-search-input
- v-model="searchQuery"
- :placeholder-text="s__('ClusterIntegration|Search machine types')"
- />
- <div class="dropdown-content">
- <ul>
- <li v-show="!results.length">
- <span class="menu-item">
- {{ s__('ClusterIntegration|No machine types matched your search') }}
- </span>
- </li>
- <li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
- </li>
- </ul>
- </div>
- <div class="dropdown-loading"><gl-loading-icon size="sm" /></div>
- </div>
- </div>
- <span
- v-if="hasErrors"
- :class="{
- 'text-danger': hasErrors,
- 'text-muted': !hasErrors,
- }"
- class="form-text"
- >
- {{ errorMessage }}
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue
deleted file mode 100644
index 8f18ac29c0f..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<script>
-import { createNamespacedHelpers, mapState, mapGetters, mapActions } from 'vuex';
-
-import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
-
-const { mapState: mapDropdownState } = createNamespacedHelpers('networks');
-const { mapActions: mapSubnetworkActions } = createNamespacedHelpers('subnetworks');
-
-export default {
- components: {
- ClusterFormDropdown,
- },
- props: {
- fieldName: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState(['selectedNetwork']),
- ...mapDropdownState(['items', 'isLoadingItems', 'loadingItemsError']),
- ...mapGetters(['hasZone', 'projectId', 'region']),
- },
- methods: {
- ...mapActions(['setNetwork', 'setSubnetwork']),
- ...mapSubnetworkActions({ fetchSubnetworks: 'fetchItems' }),
- setNetworkAndFetchSubnetworks(network) {
- const { projectId: project, region } = this;
-
- this.setSubnetwork('');
- this.setNetwork(network);
- this.fetchSubnetworks({ project, region, network: network.selfLink });
- },
- },
-};
-</script>
-<template>
- <cluster-form-dropdown
- :field-name="fieldName"
- :value="selectedNetwork"
- :items="items"
- :disabled="!hasZone"
- :loading="isLoadingItems"
- :has-errors="Boolean(loadingItemsError)"
- :loading-text="s__('ClusterIntegration|Loading networks')"
- :placeholder="s__('ClusterIntegration|Select a network')"
- :search-field-placeholder="s__('ClusterIntegration|Search networks')"
- :empty-text="s__('ClusterIntegration|No networks found')"
- :error-message="s__('ClusterIntegration|Could not load networks')"
- :disabled-text="s__('ClusterIntegration|Select a zone to choose a network')"
- @input="setNetworkAndFetchSubnetworks"
- />
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue
deleted file mode 100644
index aba6dd4b493..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue
+++ /dev/null
@@ -1,194 +0,0 @@
-<script>
-import { GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
-import { mapState, mapGetters, mapActions } from 'vuex';
-import { s__ } from '~/locale';
-
-import gkeDropdownMixin from './gke_dropdown_mixin';
-
-export default {
- name: 'GkeProjectIdDropdown',
- components: {
- GlSprintf,
- GlLink,
- GlIcon,
- },
- mixins: [gkeDropdownMixin],
- props: {
- docsUrl: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState(['selectedProject', 'isValidatingProjectBilling', 'projectHasBillingEnabled']),
- ...mapState({ items: 'projects' }),
- ...mapGetters(['hasProject']),
- hasOneProject() {
- return this.items && this.items.length === 1;
- },
- isDisabled() {
- return (
- this.isLoading || this.isValidatingProjectBilling || (this.items && this.items.length < 2)
- );
- },
- toggleText() {
- if (this.isValidatingProjectBilling) {
- return s__('ClusterIntegration|Validating project billing status');
- }
-
- if (this.isLoading) {
- return s__('ClusterIntegration|Fetching projects');
- }
-
- if (this.hasProject) {
- return this.selectedProject.name;
- }
-
- if (!this.items) {
- return s__('ClusterIntegration|No projects found');
- }
-
- return s__('ClusterIntegration|Select project');
- },
- helpText() {
- if (this.hasErrors) {
- return this.errorMessage;
- }
-
- if (!this.items) {
- return s__(
- 'ClusterIntegration|We were unable to fetch any projects. Ensure that you have a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.',
- );
- }
-
- return this.items.length
- ? s__(
- 'ClusterIntegration|To use a new project, first create one on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.',
- )
- : s__(
- 'ClusterIntegration|To create a cluster, first create a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.',
- );
- },
- errorMessage() {
- if (!this.projectHasBillingEnabled) {
- if (this.gapiError) {
- return s__(
- 'ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again.',
- );
- }
-
- return s__(
- 'ClusterIntegration|This project does not have billing enabled. To create a cluster, %{linkToBillingStart}enable billing%{linkToBillingEnd} and try again.',
- );
- }
-
- return s__(
- 'ClusterIntegration|An error occurred while trying to fetch your projects: %{error}',
- );
- },
- },
- watch: {
- selectedProject() {
- this.setIsValidatingProjectBilling(true);
-
- this.validateProjectBilling()
- .then(this.validateProjectBillingSuccessHandler)
- .catch(this.validateProjectBillingFailureHandler);
- },
- },
- created() {
- this.isLoading = true;
-
- this.fetchProjects().then(this.fetchSuccessHandler).catch(this.fetchFailureHandler);
- },
- methods: {
- ...mapActions(['fetchProjects', 'setIsValidatingProjectBilling', 'validateProjectBilling']),
- ...mapActions({ setItem: 'setProject' }),
- fetchSuccessHandler() {
- if (this.defaultValue) {
- const projectToSelect = this.items.find((item) => item.projectId === this.defaultValue);
-
- if (projectToSelect) {
- this.setItem(projectToSelect);
- }
- } else if (this.items.length === 1) {
- this.setItem(this.items[0]);
- }
-
- this.isLoading = false;
- this.hasErrors = false;
- },
- validateProjectBillingSuccessHandler() {
- this.hasErrors = !this.projectHasBillingEnabled;
- },
- validateProjectBillingFailureHandler(resp) {
- this.hasErrors = true;
-
- this.gapiError = resp.result ? resp.result.error.message : resp;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <div class="js-gcp-project-id-dropdown dropdown">
- <dropdown-hidden-input :name="fieldName" :value="selectedProject.projectId" />
- <dropdown-button
- :class="{
- 'border-danger': hasErrors,
- 'read-only': hasOneProject,
- }"
- :is-disabled="isDisabled"
- :is-loading="isLoading"
- :toggle-text="toggleText"
- />
- <div class="dropdown-menu dropdown-select">
- <dropdown-search-input
- v-model="searchQuery"
- :placeholder-text="s__('ClusterIntegration|Search projects')"
- />
- <div class="dropdown-content">
- <ul>
- <li v-show="!results.length">
- <span class="menu-item">
- {{ s__('ClusterIntegration|No projects matched your search') }}
- </span>
- </li>
- <li v-for="result in results" :key="result.project_number">
- <button type="button" @click.prevent="setItem(result)">{{ result.name }}</button>
- </li>
- </ul>
- </div>
- <div class="dropdown-loading"><gl-loading-icon size="sm" /></div>
- </div>
- </div>
- <span
- :class="{
- 'text-danger': hasErrors,
- 'text-muted': !hasErrors,
- }"
- class="form-text"
- >
- <gl-sprintf :message="helpText">
- <template #linkToBilling="{ content }">
- <gl-link
- :href="'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral'"
- target="_blank"
- >{{ content }} <gl-icon name="external-link"
- /></gl-link>
- </template>
-
- <template #docsLink="{ content }">
- <gl-link :href="docsUrl" target="_blank"
- >{{ content }} <gl-icon name="external-link"
- /></gl-link>
- </template>
-
- <template #error>
- {{ gapiError }}
- </template>
- </gl-sprintf>
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_submit_button.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_submit_button.vue
deleted file mode 100644
index a7e08a5e97f..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_submit_button.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
-import { mapGetters } from 'vuex';
-
-export default {
- computed: {
- ...mapGetters(['hasValidData']),
- },
-};
-</script>
-<template>
- <button
- type="submit"
- :disabled="!hasValidData"
- class="js-gke-cluster-creation-submit btn btn-success"
- >
- {{ s__('ClusterIntegration|Create Kubernetes cluster') }}
- </button>
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue
deleted file mode 100644
index dab4adc3789..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<script>
-import { createNamespacedHelpers, mapState, mapGetters, mapActions } from 'vuex';
-
-import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
-
-const { mapState: mapDropdownState } = createNamespacedHelpers('subnetworks');
-
-export default {
- components: {
- ClusterFormDropdown,
- },
- props: {
- fieldName: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState(['selectedSubnetwork']),
- ...mapDropdownState(['items', 'isLoadingItems', 'loadingItemsError']),
- ...mapGetters(['hasNetwork']),
- },
- methods: {
- ...mapActions(['setSubnetwork']),
- },
-};
-</script>
-<template>
- <cluster-form-dropdown
- :field-name="fieldName"
- :value="selectedSubnetwork"
- :items="items"
- :disabled="!hasNetwork"
- :loading="isLoadingItems"
- :has-errors="Boolean(loadingItemsError)"
- :loading-text="s__('ClusterIntegration|Loading subnetworks')"
- :placeholder="s__('ClusterIntegration|Select a subnetwork')"
- :search-field-placeholder="s__('ClusterIntegration|Search subnetworks')"
- :empty-text="s__('ClusterIntegration|No subnetworks found')"
- :error-message="s__('ClusterIntegration|Could not load subnetworks')"
- :disabled-text="s__('ClusterIntegration|Select a network to choose a subnetwork')"
- @input="setSubnetwork"
- />
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue
deleted file mode 100644
index 027ce74753e..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script>
-import { mapState, mapActions } from 'vuex';
-import { sprintf, s__ } from '~/locale';
-
-import gkeDropdownMixin from './gke_dropdown_mixin';
-
-export default {
- name: 'GkeZoneDropdown',
- mixins: [gkeDropdownMixin],
- computed: {
- ...mapState([
- 'selectedProject',
- 'selectedZone',
- 'projects',
- 'isValidatingProjectBilling',
- 'projectHasBillingEnabled',
- ]),
- ...mapState({ items: 'zones' }),
- isDisabled() {
- return this.isLoading || this.isValidatingProjectBilling || !this.projectHasBillingEnabled;
- },
- toggleText() {
- if (this.isLoading) {
- return s__('ClusterIntegration|Fetching zones');
- }
-
- if (this.selectedZone) {
- return this.selectedZone;
- }
-
- return !this.projectHasBillingEnabled
- ? s__('ClusterIntegration|Select project to choose zone')
- : s__('ClusterIntegration|Select zone');
- },
- errorMessage() {
- return sprintf(
- s__('ClusterIntegration|An error occurred while trying to fetch project zones: %{error}'),
- { error: this.gapiError },
- );
- },
- },
- watch: {
- isValidatingProjectBilling(isValidating) {
- this.hasErrors = false;
-
- if (!isValidating && this.projectHasBillingEnabled) {
- this.isLoading = true;
-
- this.fetchZones().then(this.fetchSuccessHandler).catch(this.fetchFailureHandler);
- }
- },
- },
- methods: {
- ...mapActions(['fetchZones']),
- ...mapActions({ setItem: 'setZone' }),
- },
-};
-</script>
-
-<template>
- <div>
- <div class="js-gcp-zone-dropdown dropdown">
- <dropdown-hidden-input :name="fieldName" :value="selectedZone" />
- <dropdown-button
- :class="{ 'border-danger': hasErrors }"
- :is-disabled="isDisabled"
- :is-loading="isLoading"
- :toggle-text="toggleText"
- />
- <div class="dropdown-menu dropdown-select">
- <dropdown-search-input
- v-model="searchQuery"
- :placeholder-text="s__('ClusterIntegration|Search zones')"
- />
- <div class="dropdown-content">
- <ul>
- <li v-show="!results.length">
- <span class="menu-item">
- {{ s__('ClusterIntegration|No zones matched your search') }}
- </span>
- </li>
- <li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
- </li>
- </ul>
- </div>
- <div class="dropdown-loading"><gl-loading-icon size="sm" /></div>
- </div>
- </div>
- <span
- v-if="hasErrors"
- :class="{
- 'text-danger': hasErrors,
- 'text-muted': !hasErrors,
- }"
- class="form-text"
- >
- {{ errorMessage }}
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/constants.js b/app/assets/javascripts/create_cluster/gke_cluster/constants.js
deleted file mode 100644
index 2a1c0819916..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/constants.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { s__ } from '~/locale';
-
-export const GCP_API_ERROR = s__(
- 'ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later.',
-);
-export const GCP_API_CLOUD_BILLING_ENDPOINT =
- 'https://www.googleapis.com/discovery/v1/apis/cloudbilling/v1/rest';
-export const GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT =
- 'https://www.googleapis.com/discovery/v1/apis/cloudresourcemanager/v1/rest';
-export const GCP_API_COMPUTE_ENDPOINT =
- 'https://www.googleapis.com/discovery/v1/apis/compute/v1/rest';
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/gapi_loader.js b/app/assets/javascripts/create_cluster/gke_cluster/gapi_loader.js
deleted file mode 100644
index b5f92fed8eb..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/gapi_loader.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// This is a helper module to lazily import the google APIs for the GKE cluster
-// integration without introducing an indirect global dependency on an
-// initialized window.gapi object.
-export default () => {
- if (window.gapiPromise === undefined) {
- // first time loading the module
- window.gapiPromise = new Promise((resolve, reject) => {
- // this callback is set as a query param to script.src URL
- window.onGapiLoad = () => {
- resolve(window.gapi);
- };
-
- const script = document.createElement('script');
- // do not use script.onload, because gapi continues to load after the initial script load
- script.type = 'text/javascript';
- script.async = true;
- script.src = 'https://apis.google.com/js/api.js?onload=onGapiLoad';
- script.onerror = reject;
- document.head.appendChild(script);
- });
- }
-
- return window.gapiPromise;
-};
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/index.js b/app/assets/javascripts/create_cluster/gke_cluster/index.js
deleted file mode 100644
index 3a42b460e1c..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/index.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import Vue from 'vue';
-import createFlash from '~/flash';
-import GkeMachineTypeDropdown from './components/gke_machine_type_dropdown.vue';
-import GkeProjectIdDropdown from './components/gke_project_id_dropdown.vue';
-import GkeSubmitButton from './components/gke_submit_button.vue';
-import GkeZoneDropdown from './components/gke_zone_dropdown.vue';
-import * as CONSTANTS from './constants';
-import gapiLoader from './gapi_loader';
-
-import store from './store';
-
-const mountComponent = (entryPoint, component, componentName, extraProps = {}) => {
- const el = document.querySelector(entryPoint);
- if (!el) return false;
-
- const hiddenInput = el.querySelector('input');
-
- return new Vue({
- el,
- store,
- components: {
- [componentName]: component,
- },
- render: (createElement) =>
- createElement(componentName, {
- props: {
- fieldName: hiddenInput.getAttribute('name'),
- fieldId: hiddenInput.getAttribute('id'),
- defaultValue: hiddenInput.value,
- ...extraProps,
- },
- }),
- });
-};
-
-const mountGkeProjectIdDropdown = () => {
- const entryPoint = '.js-gcp-project-id-dropdown-entry-point';
- const el = document.querySelector(entryPoint);
-
- mountComponent(entryPoint, GkeProjectIdDropdown, 'gke-project-id-dropdown', {
- docsUrl: el.dataset.docsurl,
- });
-};
-
-const mountGkeZoneDropdown = () => {
- mountComponent('.js-gcp-zone-dropdown-entry-point', GkeZoneDropdown, 'gke-zone-dropdown');
-};
-
-const mountGkeMachineTypeDropdown = () => {
- mountComponent(
- '.js-gcp-machine-type-dropdown-entry-point',
- GkeMachineTypeDropdown,
- 'gke-machine-type-dropdown',
- );
-};
-
-const mountGkeSubmitButton = () => {
- mountComponent('.js-gke-cluster-creation-submit-container', GkeSubmitButton, 'gke-submit-button');
-};
-
-const gkeDropdownErrorHandler = () => {
- createFlash({
- message: CONSTANTS.GCP_API_ERROR,
- });
-};
-
-const initializeGapiClient = (gapi) => () => {
- const el = document.querySelector('.js-gke-cluster-creation');
- if (!el) return false;
-
- return gapi.client
- .init({
- discoveryDocs: [
- CONSTANTS.GCP_API_CLOUD_BILLING_ENDPOINT,
- CONSTANTS.GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT,
- CONSTANTS.GCP_API_COMPUTE_ENDPOINT,
- ],
- })
- .then(() => {
- gapi.client.setToken({ access_token: el.dataset.token });
-
- mountGkeProjectIdDropdown();
- mountGkeZoneDropdown();
- mountGkeMachineTypeDropdown();
- mountGkeSubmitButton();
- })
- .catch(gkeDropdownErrorHandler);
-};
-
-const initGkeDropdowns = () =>
- gapiLoader()
- .then((gapi) => gapi.load('client', initializeGapiClient(gapi)))
- .catch(gkeDropdownErrorHandler);
-
-export default initGkeDropdowns;
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js b/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js
deleted file mode 100644
index f4c35dafc22..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import gapiLoader from '../gapi_loader';
-import * as types from './mutation_types';
-
-const gapiResourceListRequest = ({ resource, params, commit, mutation, payloadKey }) =>
- new Promise((resolve, reject) => {
- const request = resource.list(params);
-
- return request.then(
- (resp) => {
- const { result } = resp;
-
- commit(mutation, result[payloadKey]);
-
- resolve();
- },
- (resp) => {
- reject(resp);
- },
- );
- });
-
-export const setProject = ({ commit }, selectedProject) => {
- commit(types.SET_PROJECT, selectedProject);
-};
-
-export const setZone = ({ commit }, selectedZone) => {
- commit(types.SET_ZONE, selectedZone);
-};
-
-export const setMachineType = ({ commit }, selectedMachineType) => {
- commit(types.SET_MACHINE_TYPE, selectedMachineType);
-};
-
-export const setIsValidatingProjectBilling = ({ commit }, isValidatingProjectBilling) => {
- commit(types.SET_IS_VALIDATING_PROJECT_BILLING, isValidatingProjectBilling);
-};
-
-export const fetchProjects = ({ commit }) =>
- gapiLoader().then((gapi) =>
- gapiResourceListRequest({
- resource: gapi.client.cloudresourcemanager.projects,
- params: {},
- commit,
- mutation: types.SET_PROJECTS,
- payloadKey: 'projects',
- }),
- );
-
-export const validateProjectBilling = ({ dispatch, commit, state }) =>
- gapiLoader()
- .then((gapi) => {
- const request = gapi.client.cloudbilling.projects.getBillingInfo({
- name: `projects/${state.selectedProject.projectId}`,
- });
-
- commit(types.SET_ZONE, '');
- commit(types.SET_MACHINE_TYPE, '');
-
- return request;
- })
- .then(
- (resp) => {
- const { billingEnabled } = resp.result;
-
- commit(types.SET_PROJECT_BILLING_STATUS, Boolean(billingEnabled));
- dispatch('setIsValidatingProjectBilling', false);
- },
- (errorResp) => {
- dispatch('setIsValidatingProjectBilling', false);
- return errorResp;
- },
- );
-
-export const fetchZones = ({ commit, state }) =>
- gapiLoader().then((gapi) =>
- gapiResourceListRequest({
- resource: gapi.client.compute.zones,
- params: {
- project: state.selectedProject.projectId,
- },
- commit,
- mutation: types.SET_ZONES,
- payloadKey: 'items',
- }),
- );
-
-export const fetchMachineTypes = ({ commit, state }) =>
- gapiLoader().then((gapi) =>
- gapiResourceListRequest({
- resource: gapi.client.compute.machineTypes,
- params: {
- project: state.selectedProject.projectId,
- zone: state.selectedZone,
- },
- commit,
- mutation: types.SET_MACHINE_TYPES,
- payloadKey: 'items',
- }),
- );
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/getters.js b/app/assets/javascripts/create_cluster/gke_cluster/store/getters.js
deleted file mode 100644
index 99f8393ffdb..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/getters.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const hasProject = (state) => Boolean(state.selectedProject.projectId);
-export const hasZone = (state) => Boolean(state.selectedZone);
-export const hasMachineType = (state) => Boolean(state.selectedMachineType);
-export const hasValidData = (state, getters) =>
- Boolean(state.projectHasBillingEnabled) && getters.hasZone && getters.hasMachineType;
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/index.js b/app/assets/javascripts/create_cluster/gke_cluster/store/index.js
deleted file mode 100644
index 5f72060633e..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import createState from './state';
-
-Vue.use(Vuex);
-
-export const createStore = () =>
- new Vuex.Store({
- actions,
- getters,
- mutations,
- state: createState(),
- });
-
-export default createStore();
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js
deleted file mode 100644
index 45a91efc2d9..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export const SET_PROJECT = 'SET_PROJECT';
-export const SET_PROJECT_BILLING_STATUS = 'SET_PROJECT_BILLING_STATUS';
-export const SET_IS_VALIDATING_PROJECT_BILLING = 'SET_IS_VALIDATING_PROJECT_BILLING';
-export const SET_ZONE = 'SET_ZONE';
-export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE';
-export const SET_PROJECTS = 'SET_PROJECTS';
-export const SET_ZONES = 'SET_ZONES';
-export const SET_MACHINE_TYPES = 'SET_MACHINE_TYPES';
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js
deleted file mode 100644
index 88a2c1b630d..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as types from './mutation_types';
-
-export default {
- [types.SET_PROJECT](state, selectedProject) {
- Object.assign(state, { selectedProject });
- },
- [types.SET_IS_VALIDATING_PROJECT_BILLING](state, isValidatingProjectBilling) {
- Object.assign(state, { isValidatingProjectBilling });
- },
- [types.SET_PROJECT_BILLING_STATUS](state, projectHasBillingEnabled) {
- Object.assign(state, { projectHasBillingEnabled });
- },
- [types.SET_ZONE](state, selectedZone) {
- Object.assign(state, { selectedZone });
- },
- [types.SET_MACHINE_TYPE](state, selectedMachineType) {
- Object.assign(state, { selectedMachineType });
- },
- [types.SET_PROJECTS](state, projects) {
- Object.assign(state, { projects });
- },
- [types.SET_ZONES](state, zones) {
- Object.assign(state, { zones });
- },
- [types.SET_MACHINE_TYPES](state, machineTypes) {
- Object.assign(state, { machineTypes });
- },
-};
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/state.js b/app/assets/javascripts/create_cluster/gke_cluster/store/state.js
deleted file mode 100644
index 9f3c473d4bc..00000000000
--- a/app/assets/javascripts/create_cluster/gke_cluster/store/state.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default () => ({
- selectedProject: {
- projectId: '',
- name: '',
- },
- selectedZone: '',
- selectedMachineType: '',
- isValidatingProjectBilling: null,
- projectHasBillingEnabled: null,
- projects: [],
- zones: [],
- machineTypes: [],
-});
diff --git a/app/assets/javascripts/create_cluster/init_create_cluster.js b/app/assets/javascripts/create_cluster/init_create_cluster.js
deleted file mode 100644
index d367d7ec333..00000000000
--- a/app/assets/javascripts/create_cluster/init_create_cluster.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import PersistentUserCallout from '~/persistent_user_callout';
-import initGkeDropdowns from './gke_cluster';
-import initGkeNamespace from './gke_cluster_namespace';
-
-const newClusterViews = [':clusters:new', ':clusters:create_gcp', ':clusters:create_user'];
-
-const isProjectLevelCluster = (page) => page.startsWith('project:clusters');
-
-export default (document) => {
- const { page } = document.body.dataset;
- const isNewClusterView = newClusterViews.some((view) => page.endsWith(view));
-
- if (!isNewClusterView) {
- return;
- }
-
- const callout = document.querySelector('.gcp-signup-offer');
- PersistentUserCallout.factory(callout);
-
- initGkeDropdowns();
-
- if (isProjectLevelCluster(page)) {
- initGkeNamespace();
- }
-
- import(/* webpackChunkName: 'eks_cluster' */ '~/create_cluster/eks_cluster')
- .then(({ default: initCreateEKSCluster }) => {
- const el = document.querySelector('.js-create-eks-cluster-form-container');
-
- if (el) {
- initCreateEKSCluster(el);
- }
- })
- .catch(() => {});
-};
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/actions.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/actions.js
deleted file mode 100644
index 669b0dcc732..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/actions.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as types from './mutation_types';
-
-export default (fetchItems) => ({
- requestItems: ({ commit }) => commit(types.REQUEST_ITEMS),
- receiveItemsSuccess: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_SUCCESS, payload),
- receiveItemsError: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_ERROR, payload),
- fetchItems: ({ dispatch }, payload) => {
- dispatch('requestItems');
-
- return fetchItems(payload)
- .then((items) => dispatch('receiveItemsSuccess', { items }))
- .catch((error) => dispatch('receiveItemsError', { error }));
- },
-});
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/getters.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/getters.js
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/getters.js
+++ /dev/null
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/index.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/index.js
deleted file mode 100644
index de8cc44fa7c..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import state from './state';
-
-const createStore = ({ fetchFn, initialState }) => ({
- actions: actions(fetchFn),
- getters,
- mutations,
- state: Object.assign(state(), initialState || {}),
-});
-
-export default createStore;
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutation_types.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutation_types.js
deleted file mode 100644
index 48959a73924..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutation_types.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const REQUEST_ITEMS = 'REQUEST_ITEMS';
-export const RECEIVE_ITEMS_SUCCESS = 'REQUEST_ITEMS_SUCCESS';
-export const RECEIVE_ITEMS_ERROR = 'RECEIVE_ITEMS_ERROR';
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutations.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutations.js
deleted file mode 100644
index d09689f1f6c..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/mutations.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as types from './mutation_types';
-
-export default {
- [types.REQUEST_ITEMS](state) {
- state.isLoadingItems = true;
- state.loadingItemsError = null;
- },
- [types.RECEIVE_ITEMS_SUCCESS](state, { items }) {
- state.isLoadingItems = false;
- state.items = items;
- },
- [types.RECEIVE_ITEMS_ERROR](state, { error }) {
- state.isLoadingItems = false;
- state.loadingItemsError = error;
- },
-};
diff --git a/app/assets/javascripts/create_cluster/store/cluster_dropdown/state.js b/app/assets/javascripts/create_cluster/store/cluster_dropdown/state.js
deleted file mode 100644
index b949a24216e..00000000000
--- a/app/assets/javascripts/create_cluster/store/cluster_dropdown/state.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default () => ({
- isLoadingItems: false,
- items: [],
- loadingItemsError: null,
-});
diff --git a/app/assets/javascripts/crm/components/form.vue b/app/assets/javascripts/crm/components/form.vue
index 4f94898ff63..72def54aedf 100644
--- a/app/assets/javascripts/crm/components/form.vue
+++ b/app/assets/javascripts/crm/components/form.vue
@@ -1,5 +1,5 @@
<script>
-import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { get as getPropValueByPath, isEmpty } from 'lodash';
import { produce } from 'immer';
import { MountingPortal } from 'portal-vue';
@@ -28,6 +28,7 @@ export default {
GlDrawer,
GlFormGroup,
GlFormInput,
+ GlFormSelect,
MountingPortal,
},
props: {
@@ -136,15 +137,25 @@ export default {
methods: {
setInitialModel() {
const existingModel = this.records.find(({ id }) => id === this.existingId);
+ const noModel = !this.isEditMode || !existingModel;
this.model = this.fields.reduce(
(map, field) =>
Object.assign(map, {
- [field.name]: !this.isEditMode || !existingModel ? null : existingModel[field.name],
+ [field.name]: noModel ? null : this.extractValue(existingModel, field.name),
}),
{},
);
},
+ extractValue(existingModel, fieldName) {
+ const value = existingModel[fieldName];
+ if (value != null) return value;
+
+ /* eslint-disable-next-line @gitlab/require-i18n-strings */
+ if (!fieldName.endsWith('Id')) return null;
+
+ return existingModel[fieldName.slice(0, -2)]?.id;
+ },
formatValue(model, field) {
if (!isEmpty(model[field.name]) && field.input?.type === 'number') {
return parseFloat(model[field.name]);
@@ -216,6 +227,15 @@ export default {
return data[keys[0]];
},
+ getDrawerHeaderHeight() {
+ const wrapperEl = document.querySelector('.content-wrapper');
+
+ if (wrapperEl) {
+ return `${wrapperEl.offsetTop}px`;
+ }
+
+ return '';
+ },
},
MSG_CANCEL,
INDEX_ROUTE_NAME,
@@ -224,7 +244,12 @@ export default {
<template>
<mounting-portal v-if="!loading" mount-to="#js-crm-form-portal" append>
- <gl-drawer class="gl-drawer-responsive gl-absolute" :open="drawerOpen" @close="close(false)">
+ <gl-drawer
+ :header-height="getDrawerHeaderHeight()"
+ class="gl-drawer-responsive"
+ :open="drawerOpen"
+ @close="close(false)"
+ >
<template #title>
<h3>{{ title }}</h3>
</template>
@@ -242,7 +267,13 @@ export default {
:label="getFieldLabel(field)"
:label-for="field.name"
>
- <gl-form-input :id="field.name" v-bind="field.input" v-model="model[field.name]" />
+ <gl-form-select
+ v-if="field.values"
+ :id="field.name"
+ v-model="model[field.name]"
+ :options="field.values"
+ />
+ <gl-form-input v-else :id="field.name" v-bind="field.input" v-model="model[field.name]" />
</gl-form-group>
<span class="gl-float-right">
<gl-button data-testid="cancel-button" @click="close(false)">
diff --git a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
index 58eaabfbb7f..f114ffedfe6 100644
--- a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
+++ b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
@@ -3,6 +3,7 @@ import { s__, __ } from '~/locale';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_CRM_CONTACT, TYPE_GROUP } from '~/graphql_shared/constants';
import ContactForm from '../../components/form.vue';
+import getGroupOrganizationsQuery from '../../organizations/components/graphql/get_group_organizations.query.graphql';
import getGroupContactsQuery from './graphql/get_group_contacts.query.graphql';
import createContactMutation from './graphql/create_contact.mutation.graphql';
import updateContactMutation from './graphql/update_contact.mutation.graphql';
@@ -19,6 +20,26 @@ export default {
default: false,
},
},
+ data() {
+ return {
+ organizations: [],
+ };
+ },
+ apollo: {
+ organizations: {
+ query() {
+ return getGroupOrganizationsQuery;
+ },
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ };
+ },
+ update(data) {
+ return this.extractOrganizations(data);
+ },
+ },
+ },
computed: {
contactGraphQLId() {
if (!this.isEditMode) return null;
@@ -52,14 +73,35 @@ export default {
additionalCreateParams() {
return { groupId: this.groupGraphQLId };
},
+ fields() {
+ return [
+ { name: 'firstName', label: __('First name'), required: true },
+ { name: 'lastName', label: __('Last name'), required: true },
+ { name: 'email', label: __('Email'), required: true },
+ { name: 'phone', label: __('Phone') },
+ {
+ name: 'organizationId',
+ label: s__('Crm|Organization'),
+ values: this.organizationSelectValues,
+ },
+ { name: 'description', label: __('Description') },
+ ];
+ },
+ organizationSelectValues() {
+ const values = this.organizations.map((o) => {
+ return { value: o.id, text: o.name };
+ });
+
+ values.unshift({ value: null, text: s__('Crm|No organization') });
+ return values;
+ },
+ },
+ methods: {
+ extractOrganizations(data) {
+ const organizations = data?.group?.organizations?.nodes || [];
+ return organizations.slice().sort((a, b) => a.name.localeCompare(b.name));
+ },
},
- fields: [
- { name: 'firstName', label: __('First name'), required: true },
- { name: 'lastName', label: __('Last name'), required: true },
- { name: 'email', label: __('Email'), required: true },
- { name: 'phone', label: __('Phone') },
- { name: 'description', label: __('Description') },
- ],
};
</script>
@@ -71,7 +113,7 @@ export default {
:mutation="mutation"
:additional-create-params="additionalCreateParams"
:existing-id="contactGraphQLId"
- :fields="$options.fields"
+ :fields="fields"
:title="title"
:success-message="successMessage"
/>
diff --git a/app/assets/javascripts/crm/contacts/components/contacts_root.vue b/app/assets/javascripts/crm/contacts/components/contacts_root.vue
index 17be3800256..9d6f34c73b7 100644
--- a/app/assets/javascripts/crm/contacts/components/contacts_root.vue
+++ b/app/assets/javascripts/crm/contacts/components/contacts_root.vue
@@ -55,7 +55,7 @@ export default {
return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName));
},
getIssuesPath(path, value) {
- return `${path}?scope=all&state=opened&crm_contact_id=${value}`;
+ return `${path}?crm_contact_id=${value}`;
},
getEditRoute(id) {
return { name: this.$options.EDIT_ROUTE_NAME, params: { id } };
diff --git a/app/assets/javascripts/crm/organizations/components/organizations_root.vue b/app/assets/javascripts/crm/organizations/components/organizations_root.vue
index 522e29eb2af..a165dd68603 100644
--- a/app/assets/javascripts/crm/organizations/components/organizations_root.vue
+++ b/app/assets/javascripts/crm/organizations/components/organizations_root.vue
@@ -55,7 +55,7 @@ export default {
return organizations.slice().sort((a, b) => a.name.localeCompare(b.name));
},
getIssuesPath(path, value) {
- return `${path}?scope=all&state=opened&crm_organization_id=${value}`;
+ return `${path}?crm_organization_id=${value}`;
},
getEditRoute(id) {
return { name: this.$options.EDIT_ROUTE_NAME, params: { id } };
diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue
index 3d7a34581b3..1883030e51f 100644
--- a/app/assets/javascripts/cycle_analytics/components/base.vue
+++ b/app/assets/javascripts/cycle_analytics/components/base.vue
@@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
+import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants';
import { toYmd } from '~/analytics/shared/utils';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
@@ -150,34 +151,36 @@ export default {
pageTitle: __('Value Stream Analytics'),
recentActivity: __('Recent Project Activity'),
},
+ VSA_METRICS_GROUPS,
};
</script>
<template>
<div>
<h3>{{ $options.i18n.pageTitle }}</h3>
+ <value-stream-filters
+ :group-id="endpoints.groupId"
+ :group-path="endpoints.groupPath"
+ :has-project-filter="false"
+ :start-date="createdAfter"
+ :end-date="createdBefore"
+ @setDateRange="onSetDateRange"
+ />
<div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row">
<path-navigation
v-if="displayPathNavigation"
data-testid="vsa-path-navigation"
- class="gl-w-full gl-pb-2"
+ class="gl-w-full gl-mt-4"
:loading="isLoading || isLoadingStage"
:stages="pathNavigationData"
:selected-stage="selectedStage"
@selected="onSelectStage"
/>
</div>
- <value-stream-filters
- :group-id="endpoints.groupId"
- :group-path="endpoints.groupPath"
- :has-project-filter="false"
- :start-date="createdAfter"
- :end-date="createdBefore"
- @setDateRange="onSetDateRange"
- />
<value-stream-metrics
:request-path="endpoints.fullPath"
:request-params="filterParams"
:requests="metricsRequests"
+ :group-by="$options.VSA_METRICS_GROUPS"
/>
<gl-loading-icon v-if="isLoading" size="lg" />
<stage-table
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
index 6a45969fd1a..e4236968efc 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
@@ -191,9 +191,7 @@ export default {
/>
<gl-table
v-else
- head-variant="white"
stacked="lg"
- thead-class="border-bottom"
show-empty
:sort-by.sync="sort"
:sort-direction.sync="direction"
diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
index 66bccf19496..f686cd0db95 100644
--- a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
+++ b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
@@ -1,21 +1,13 @@
<script>
-import { GlIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
import DateRange from '~/analytics/shared/components/daterange.vue';
import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
import { DATE_RANGE_LIMIT, PROJECTS_PER_PAGE } from '~/analytics/shared/constants';
import FilterBar from './filter_bar.vue';
-export const AGGREGATION_TOGGLE_LABEL = s__('CycleAnalytics|Filter by stop date');
-export const AGGREGATION_DESCRIPTION = s__(
- 'CycleAnalytics|When enabled, the results show items with a stop event within the date range. When disabled, the results show items with a start event within the date range.',
-);
-
export default {
name: 'ValueStreamFilters',
components: {
- GlIcon,
- GlToggle,
DateRange,
ProjectsDropdownFilter,
FilterBar,
@@ -57,21 +49,6 @@ export default {
required: false,
default: null,
},
- canToggleAggregation: {
- type: Boolean,
- required: false,
- default: false,
- },
- isAggregationEnabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- isUpdatingAggregationData: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
projectsQueryParams() {
@@ -81,19 +58,8 @@ export default {
};
},
},
- methods: {
- onUpdateAggregation(ev) {
- if (!this.isUpdatingAggregationData) {
- this.$emit('toggleAggregation', ev);
- }
- },
- },
multiProjectSelect: true,
maxDateRange: DATE_RANGE_LIMIT,
- i18n: {
- AGGREGATION_TOGGLE_LABEL,
- AGGREGATION_DESCRIPTION,
- },
};
</script>
<template>
@@ -123,27 +89,6 @@ export default {
/>
</div>
<div class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row">
- <div
- v-if="canToggleAggregation"
- class="gl-display-flex gl-text-align-center gl-my-2 gl-lg-mt-0 gl-lg-mb-0 gl-mr-5"
- >
- <gl-toggle
- class="gl-flex-direction-row"
- :value="isAggregationEnabled"
- :label="$options.i18n.AGGREGATION_TOGGLE_LABEL"
- :disabled="isUpdatingAggregationData"
- label-position="left"
- @change="onUpdateAggregation"
- >
- <template #label>
- {{ $options.i18n.AGGREGATION_TOGGLE_LABEL }}&nbsp;<gl-icon
- v-gl-tooltip.hover
- :title="$options.i18n.AGGREGATION_DESCRIPTION"
- name="information-o"
- />
- </template>
- </gl-toggle>
- </div>
<date-range
v-if="hasDateRangeFilter"
:start-date="startDate"
diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
index 7acb5549273..814a4b672a2 100644
--- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
+++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
@@ -137,7 +137,7 @@ export default {
v-model="freezeStartCron"
class="gl-font-monospace!"
data-qa-selector="deploy_freeze_start_field"
- :placeholder="this.$options.translations.cronPlaceholder"
+ :placeholder="$options.translations.cronPlaceholder"
:state="freezeStartCronState"
trim
/>
@@ -154,7 +154,7 @@ export default {
v-model="freezeEndCron"
class="gl-font-monospace!"
data-qa-selector="deploy_freeze_end_field"
- :placeholder="this.$options.translations.cronPlaceholder"
+ :placeholder="$options.translations.cronPlaceholder"
:state="freezeEndCronState"
trim
/>
diff --git a/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
index 124f12ef018..c235e7fbf3d 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql
@@ -2,7 +2,7 @@
#import "../fragments/discussion_resolved_status.fragment.graphql"
#import "../fragments/design_todo_item.fragment.graphql"
-mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
+mutation toggleResolveDiscussion($id: DiscussionID!, $resolve: Boolean!) {
discussionToggleResolve(input: { id: $id, resolve: $resolve }) {
discussion {
id
diff --git a/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
index 34d683ac1ee..3200327e03d 100644
--- a/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
+++ b/app/assets/javascripts/design_management/graphql/mutations/upload_design.mutation.graphql
@@ -3,7 +3,6 @@
mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
designManagementUpload(input: { projectPath: $projectPath, iid: $iid, files: $files }) {
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
designs {
...DesignItem
versions {
diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
index a5394457f73..730467c33f6 100644
--- a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
@@ -13,7 +13,6 @@ query getDesign(
id
designCollection {
designs(atVersion: $atVersion, filenames: $filenames) {
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
nodes {
...DesignItem
issue {
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index 837320b9423..2b395921ee1 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -349,7 +349,9 @@ export default {
class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative"
>
<design-destroyer
- :filenames="[design.filename]"
+ :filenames="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ design.filename,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:project-path="projectPath"
:iid="issueIid"
@done="$router.push({ name: $options.DESIGNS_ROUTE_NAME })"
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index c86f2c8451c..c3436159cea 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -17,7 +17,6 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import { parseBoolean } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
-import MrWidgetHowToMergeModal from '~/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -75,7 +74,6 @@ export default {
PanelResizer,
GlPagination,
GlSprintf,
- MrWidgetHowToMergeModal,
GlAlert,
},
mixins: [glFeatureFlagsMixin()],
@@ -373,7 +371,7 @@ export default {
events.push(TRACKING_MULTIPLE_FILES_MODE);
}
- queueRedisHllEvents(events);
+ queueRedisHllEvents(events, { verifyCap: true });
this.subscribeToVirtualScrollingEvents();
},
@@ -738,15 +736,6 @@ export default {
/>
</div>
</div>
- <mr-widget-how-to-merge-modal
- :is-fork="isForked"
- :can-merge="canMerge"
- :source-branch="branchName"
- :source-project-path="sourceProjectFullPath"
- :target-branch="targetBranchName"
- :source-project-default-url="sourceProjectDefaultUrl"
- :reviewing-docs-path="$options.howToMergeDocsPath"
- />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue b/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
index 6c5973b7c28..fd219a7d00f 100644
--- a/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
+++ b/app/assets/javascripts/diffs/components/compare_dropdown_layout.vue
@@ -24,7 +24,12 @@ export default {
</script>
<template>
- <gl-dropdown :text="selectedVersionName" data-qa-selector="dropdown_content">
+ <gl-dropdown
+ :text="selectedVersionName"
+ data-qa-selector="dropdown_content"
+ size="small"
+ category="tertiary"
+ >
<template v-for="version in versions">
<gl-dropdown-divider v-if="version.addDivider" :key="version.id" />
<gl-dropdown-item
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 4dfd672f99b..8a5325cf218 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -79,7 +79,7 @@ export default {
</script>
<template>
- <div class="mr-version-controls border-top">
+ <div class="mr-version-controls">
<div class="mr-version-menus-container content-block">
<gl-button
v-if="hasChanges"
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index b4bffdcb07f..1eba12a3ae9 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -156,7 +156,7 @@ export default {
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
- :img-size="40"
+ :img-size="48"
class="d-none d-sm-block new-comment"
/>
<diff-discussions
diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
index 4c7b8e8f667..4e7dc578193 100644
--- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
@@ -1,42 +1,33 @@
<script>
-import { GlIcon } from '@gitlab/ui';
-import { mapState, mapActions } from 'vuex';
+import { GlTooltipDirective, GlSafeHtmlDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { mapActions } from 'vuex';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { UNFOLD_COUNT, INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_LINES_KEY } from '../constants';
+import { UNFOLD_COUNT, INLINE_DIFF_LINES_KEY } from '../constants';
import * as utils from '../store/utils';
const EXPAND_ALL = 0;
const EXPAND_UP = 1;
const EXPAND_DOWN = 2;
-const lineNumberByViewType = (viewType, diffLine) => {
- const numberGetters = {
- [INLINE_DIFF_VIEW_TYPE]: (line) => line?.new_line,
- };
- const numberGetter = numberGetters[viewType];
- return numberGetter && numberGetter(diffLine);
-};
-
-const i18n = {
- showMore: sprintf(s__('Diffs|Show %{unfoldCount} lines'), { unfoldCount: UNFOLD_COUNT }),
- showAll: s__('Diffs|Show all unchanged lines'),
-};
-
export default {
- i18n,
+ i18n: {
+ showMore: sprintf(s__('Diffs|Show %{unfoldCount} lines'), { unfoldCount: UNFOLD_COUNT }),
+ showAll: s__('Diffs|Show all unchanged lines'),
+ },
components: {
GlIcon,
+ GlLoadingIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ SafeHtml: GlSafeHtmlDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
- fileHash: {
- type: String,
- required: true,
- },
- contextLinesPath: {
- type: String,
+ file: {
+ type: Object,
required: true,
},
line: {
@@ -53,34 +44,45 @@ export default {
required: false,
default: false,
},
+ inline: {
+ type: Boolean,
+ required: true,
+ },
+ lineCountBetween: {
+ type: Number,
+ required: false,
+ default: -1,
+ },
+ },
+ data() {
+ return { loading: { up: false, down: false, all: false } };
},
computed: {
- ...mapState({
- diffFiles: (state) => state.diffs.diffFiles,
- }),
canExpandUp() {
return !this.isBottom;
},
canExpandDown() {
return this.isBottom || !this.isTop;
},
- },
- created() {
- this.EXPAND_DOWN = EXPAND_DOWN;
- this.EXPAND_UP = EXPAND_UP;
+ isLineCountSmall() {
+ return this.lineCountBetween >= 20 || this.lineCountBetween === -1;
+ },
+ showExpandDown() {
+ return this.canExpandDown && this.isLineCountSmall;
+ },
+ showExpandUp() {
+ return this.canExpandUp && this.isLineCountSmall;
+ },
},
methods: {
...mapActions('diffs', ['loadMoreLines']),
getPrevLineNumber(oldLineNumber, newLineNumber) {
- const diffFile = utils.findDiffFile(this.diffFiles, this.fileHash);
- const index = utils.getPreviousLineIndex(INLINE_DIFF_VIEW_TYPE, diffFile, {
+ const index = utils.getPreviousLineIndex(this.file, {
oldLineNumber,
newLineNumber,
});
- return (
- lineNumberByViewType(INLINE_DIFF_VIEW_TYPE, diffFile[INLINE_DIFF_LINES_KEY][index - 2]) || 0
- );
+ return this.file[INLINE_DIFF_LINES_KEY][index - 2]?.new_line || 0;
},
callLoadMoreLines(
endpoint,
@@ -99,6 +101,9 @@ export default {
message: s__('Diffs|Something went wrong while fetching diff lines.'),
});
this.isRequesting = false;
+ })
+ .finally(() => {
+ this.loading = { up: false, down: false, all: false };
});
},
handleExpandLines(type = EXPAND_ALL) {
@@ -107,25 +112,26 @@ export default {
}
this.isRequesting = true;
- const endpoint = this.contextLinesPath;
- const { fileHash } = this;
- const view = INLINE_DIFF_VIEW_TYPE;
+ const endpoint = this.file.context_lines_path;
const oldLineNumber = this.line.meta_data.old_pos || 0;
const newLineNumber = this.line.meta_data.new_pos || 0;
const offset = newLineNumber - oldLineNumber;
- const expandOptions = { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset };
+ const expandOptions = { endpoint, oldLineNumber, newLineNumber, offset };
if (type === EXPAND_UP) {
+ this.loading.up = true;
this.handleExpandUpLines(expandOptions);
} else if (type === EXPAND_DOWN) {
+ this.loading.down = true;
this.handleExpandDownLines(expandOptions);
} else {
+ this.loading.all = true;
this.handleExpandAllLines(expandOptions);
}
},
handleExpandUpLines(expandOptions) {
- const { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset } = expandOptions;
+ const { endpoint, oldLineNumber, newLineNumber, offset } = expandOptions;
const bottom = this.isBottom;
const lineNumber = newLineNumber - 1;
@@ -139,15 +145,13 @@ export default {
unfold = false;
}
- const params = { since, to, bottom, offset, unfold, view };
+ const params = { since, to, bottom, offset, unfold };
const lineNumbers = { oldLineNumber, newLineNumber };
- this.callLoadMoreLines(endpoint, params, lineNumbers, fileHash);
+ this.callLoadMoreLines(endpoint, params, lineNumbers, this.file.file_hash);
},
handleExpandDownLines(expandOptions) {
const {
endpoint,
- fileHash,
- view,
oldLineNumber: metaOldPos,
newLineNumber: metaNewPos,
offset,
@@ -183,19 +187,19 @@ export default {
}
}
- const params = { since, to, bottom, offset, unfold, view };
+ const params = { since, to, bottom, offset, unfold };
const lineNumbers = { oldLineNumber, newLineNumber };
this.callLoadMoreLines(
endpoint,
params,
lineNumbers,
- fileHash,
+ this.file.file_hash,
isExpandDown,
nextLineNumbers,
);
},
handleExpandAllLines(expandOptions) {
- const { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset } = expandOptions;
+ const { endpoint, oldLineNumber, newLineNumber, offset } = expandOptions;
const bottom = this.isBottom;
const unfold = false;
let since;
@@ -213,21 +217,71 @@ export default {
to = newLineNumber - 1;
}
- const params = { since, to, bottom, offset, unfold, view };
+ const params = { since, to, bottom, offset, unfold };
const lineNumbers = { oldLineNumber, newLineNumber };
- this.callLoadMoreLines(endpoint, params, lineNumbers, fileHash);
+ this.callLoadMoreLines(endpoint, params, lineNumbers, this.file.file_hash);
},
},
+ EXPAND_DOWN,
+ EXPAND_UP,
};
</script>
<template>
- <div class="content js-line-expansion-content">
+ <div
+ v-if="glFeatures.updatedDiffExpansionButtons"
+ class="diff-grid-row diff-grid-row-full diff-tr line_holder match expansion"
+ >
+ <div :class="{ parallel: !inline }" class="diff-grid-left diff-grid-2-col left-side">
+ <div
+ class="diff-td diff-line-num gl-text-center! gl-p-0! gl-w-full! gl-display-flex gl-flex-direction-column"
+ >
+ <button
+ v-if="showExpandDown"
+ v-gl-tooltip.left
+ :title="s__('Diffs|Next 20 lines')"
+ type="button"
+ class="js-unfold-down gl-rounded-0 gl-border-0 diff-line-expand-button"
+ @click="handleExpandLines($options.EXPAND_DOWN)"
+ >
+ <gl-loading-icon v-if="loading.down" size="sm" color="dark" inline />
+ <gl-icon v-else name="expand-down" />
+ </button>
+ <button
+ v-if="lineCountBetween !== -1 && lineCountBetween < 20"
+ v-gl-tooltip.left
+ :title="s__('Diffs|Expand all lines')"
+ type="button"
+ class="js-unfold-all gl-rounded-0 gl-border-0 diff-line-expand-button"
+ @click="handleExpandLines()"
+ >
+ <gl-loading-icon v-if="loading.all" size="sm" color="dark" inline />
+ <gl-icon v-else name="expand" />
+ </button>
+ <button
+ v-if="showExpandUp"
+ v-gl-tooltip.left
+ :title="s__('Diffs|Previous 20 lines')"
+ type="button"
+ class="js-unfold gl-rounded-0 gl-border-0 diff-line-expand-button"
+ @click="handleExpandLines($options.EXPAND_UP)"
+ >
+ <gl-loading-icon v-if="loading.up" size="sm" color="dark" inline />
+ <gl-icon v-else name="expand-up" />
+ </button>
+ </div>
+ <div
+ v-safe-html="line.rich_text"
+ class="gl-display-flex! gl-flex-direction-column gl-justify-content-center diff-td line_content left-side gl-white-space-normal!"
+ ></div>
+ </div>
+ </div>
+ <div v-else class="content js-line-expansion-content">
<button
type="button"
:disabled="!canExpandDown"
class="js-unfold-down gl-mx-2 gl-py-4 gl-cursor-pointer"
- @click="handleExpandLines(EXPAND_DOWN)"
+ @click="handleExpandLines($options.EXPAND_DOWN)"
>
<gl-icon :size="12" name="expand-down" />
<span>{{ $options.i18n.showMore }}</span>
@@ -244,7 +298,7 @@ export default {
type="button"
:disabled="!canExpandUp"
class="js-unfold gl-mx-2 gl-py-4 gl-cursor-pointer"
- @click="handleExpandLines(EXPAND_UP)"
+ @click="handleExpandLines($options.EXPAND_UP)"
>
<gl-icon :size="12" name="expand-up" />
<span>{{ $options.i18n.showMore }}</span>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index d8f27a967df..0b82be7140c 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -5,7 +5,6 @@ import {
GlSafeHtmlDirective as SafeHtml,
GlSprintf,
GlAlert,
- GlModalDirective,
} from '@gitlab/ui';
import { escape } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
@@ -42,7 +41,6 @@ export default {
},
directives: {
SafeHtml,
- GlModalDirective,
},
mixins: [glFeatureFlagsMixin(), IdState({ idProp: (vm) => vm.file.file_hash })],
props: {
@@ -360,10 +358,12 @@ export default {
class="js-file-fork-suggestion-section file-fork-suggestion"
>
<span v-safe-html="forkMessage" class="file-fork-suggestion-note"></span>
- <a
+ <gl-button
:href="file.fork_path"
- class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success"
- >{{ $options.i18n.fork }}</a
+ class="js-fork-suggestion-button"
+ category="secondary"
+ variant="confirm"
+ >{{ $options.i18n.fork }}</gl-button
>
<button
class="js-cancel-fork-suggestion-button btn btn-grouped"
@@ -379,6 +379,53 @@ export default {
:class="hasBodyClasses.contentByHash"
data-testid="content-area"
>
+ <gl-alert
+ v-if="!showLoadingIcon && file.conflict_type"
+ variant="danger"
+ :dismissible="false"
+ data-testid="conflictsAlert"
+ >
+ {{ $options.CONFLICT_TEXT[file.conflict_type] }}
+ <template v-if="!canMerge">
+ {{ __('Ask someone with write access to resolve it.') }}
+ </template>
+ <gl-sprintf
+ v-else-if="conflictResolutionPath"
+ :message="
+ __(
+ 'You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.',
+ )
+ "
+ >
+ <template #gitlabLink="{ content }">
+ <gl-button
+ :href="conflictResolutionPath"
+ variant="link"
+ class="gl-vertical-align-text-bottom"
+ >{{ content }}</gl-button
+ >
+ </template>
+ <template #resolveLocally="{ content }">
+ <gl-button
+ variant="link"
+ class="gl-vertical-align-text-bottom js-check-out-modal-trigger"
+ >{{ content }}</gl-button
+ >
+ </template>
+ </gl-sprintf>
+ <gl-sprintf
+ v-else
+ :message="__('You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.')"
+ >
+ <template #resolveLocally="{ content }">
+ <gl-button
+ variant="link"
+ class="gl-vertical-align-text-bottom js-check-out-modal-trigger"
+ >{{ content }}</gl-button
+ >
+ </template>
+ </gl-sprintf>
+ </gl-alert>
<gl-loading-icon
v-if="showLoadingIcon"
size="sm"
@@ -402,55 +449,6 @@ export default {
<div v-else v-safe-html="errorMessage" class="nothing-here-block"></div>
</div>
<template v-else>
- <gl-alert
- v-if="file.conflict_type"
- variant="danger"
- :dismissible="false"
- data-testid="conflictsAlert"
- >
- {{ $options.CONFLICT_TEXT[file.conflict_type] }}
- <template v-if="!canMerge">
- {{ __('Ask someone with write access to resolve it.') }}
- </template>
- <gl-sprintf
- v-else-if="conflictResolutionPath"
- :message="
- __(
- 'You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.',
- )
- "
- >
- <template #gitlabLink="{ content }">
- <gl-button
- :href="conflictResolutionPath"
- variant="link"
- class="gl-vertical-align-text-bottom"
- >{{ content }}</gl-button
- >
- </template>
- <template #resolveLocally="{ content }">
- <gl-button
- v-gl-modal-directive="'modal-merge-info'"
- variant="link"
- class="gl-vertical-align-text-bottom"
- >{{ content }}</gl-button
- >
- </template>
- </gl-sprintf>
- <gl-sprintf
- v-else
- :message="__('You can %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}.')"
- >
- <template #resolveLocally="{ content }">
- <gl-button
- v-gl-modal-directive="'modal-merge-info'"
- variant="link"
- class="gl-vertical-align-text-bottom"
- >{{ content }}</gl-button
- >
- </template>
- </gl-sprintf>
- </gl-alert>
<div
v-if="showWarning"
class="collapsed-file-warning gl-p-7 gl-bg-orange-50 gl-text-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 8cdbd2b7dbc..a75262ee303 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -302,6 +302,7 @@ export default {
@click="handleFileNameClick"
>
<file-icon
+ v-if="!glFeatures.removeDiffHeaderIcons"
:file-name="filePath"
:size="16"
aria-hidden="true"
@@ -394,6 +395,7 @@ export default {
<gl-dropdown
v-gl-tooltip.hover.focus="$options.i18n.optionsDropdownTitle"
size="small"
+ category="tertiary"
right
toggle-class="btn-icon js-diff-more-actions"
class="gl-pt-0!"
@@ -402,7 +404,7 @@ export default {
@hidden="setMoreActionsShown(false)"
>
<template #button-content>
- <gl-icon name="ellipsis_v" class="mr-0" :size="12" />
+ <gl-icon name="ellipsis_v" class="mr-0" />
<span class="sr-only">{{ $options.i18n.optionsDropdownTitle }}</span>
</template>
<gl-dropdown-item
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 7a30740e31b..a2f0e2c2653 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -179,7 +179,10 @@ export default {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
- const confirmed = await confirmAction(msg);
+ const confirmed = await confirmAction(msg, {
+ primaryBtnText: __('Discard changes'),
+ cancelBtnText: __('Continue editing'),
+ });
if (!confirmed) {
return;
diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue
index 4893803a3b6..1b07b00d725 100644
--- a/app/assets/javascripts/diffs/components/diff_row.vue
+++ b/app/assets/javascripts/diffs/components/diff_row.vue
@@ -160,7 +160,13 @@ export default {
<!-- eslint-disable-next-line vue/no-deprecated-functional-template -->
<template functional>
- <div :class="$options.classNameMap(props)" class="diff-grid-row diff-tr line_holder">
+ <div
+ :class="[
+ $options.classNameMap(props),
+ { expansion: props.line.left && props.line.left.type === 'expanded' },
+ ]"
+ class="diff-grid-row diff-tr line_holder"
+ >
<div
:id="props.line.left && props.line.left.line_code"
data-testid="left-side"
diff --git a/app/assets/javascripts/diffs/components/diff_row_utils.js b/app/assets/javascripts/diffs/components/diff_row_utils.js
index 99999445c43..f610ac979ca 100644
--- a/app/assets/javascripts/diffs/components/diff_row_utils.js
+++ b/app/assets/javascripts/diffs/components/diff_row_utils.js
@@ -10,6 +10,7 @@ import {
CONFLICT_MARKER_THEIR,
CONFLICT_THEIR,
CONFLICT_OUR,
+ EXPANDED_LINE_TYPE,
} from '../constants';
export const isHighlighted = (highlightedRow, line, isCommented) => {
@@ -118,10 +119,12 @@ export const mapParallel = (content) => (line) => {
if (right) {
right = {
...right,
- renderDiscussion: Boolean(hasExpandedDiscussionOnRight && right.type),
+ renderDiscussion: Boolean(
+ hasExpandedDiscussionOnRight && right.type && right.type !== EXPANDED_LINE_TYPE,
+ ),
hasDraft: content.hasParallelDraftRight(content.diffFile.file_hash, line),
lineDraft: content.draftForLine(content.diffFile.file_hash, line, 'right'),
- hasCommentForm: Boolean(right.hasForm && right.type),
+ hasCommentForm: Boolean(right.hasForm && right.type && right.type !== EXPANDED_LINE_TYPE),
emptyCellClassMap: { conflict_their: line.left?.type === CONFLICT_OUR },
addCommentTooltip: addCommentTooltip(line.right),
};
diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue
index f46b0a538f1..529f8e0a2f9 100644
--- a/app/assets/javascripts/diffs/components/diff_view.vue
+++ b/app/assets/javascripts/diffs/components/diff_view.vue
@@ -141,6 +141,18 @@ export default {
table.classList.add(`${lineClass}-selected`);
}
},
+ getCountBetweenIndex(index) {
+ if (index === 0) {
+ return -1;
+ } else if (!this.diffLines[index + 1]) {
+ return -1;
+ }
+
+ return (
+ Number(this.diffLines[index + 1].left.new_line) -
+ Number(this.diffLines[index - 1].left.new_line)
+ );
+ },
},
userColorScheme: window.gon.user_color_scheme,
};
@@ -158,38 +170,51 @@ export default {
>
<template v-for="(line, index) in diffLines">
<template v-if="line.isMatchLineLeft || line.isMatchLineRight">
- <div :key="`expand-${index}`" class="diff-tr line_expansion match">
- <div class="diff-td text-center gl-font-regular">
- <diff-expansion-cell
- :file-hash="diffFile.file_hash"
- :context-lines-path="diffFile.context_lines_path"
- :line="line.left"
- :is-top="index === 0"
- :is-bottom="index + 1 === diffLinesLength"
- />
+ <diff-expansion-cell
+ v-if="glFeatures.updatedDiffExpansionButtons"
+ :key="`expand-${index}`"
+ :file="diffFile"
+ :line="line.left"
+ :is-top="index === 0"
+ :is-bottom="index + 1 === diffLinesLength"
+ :inline="inline"
+ :line-count-between="getCountBetweenIndex(index)"
+ />
+ <template v-else>
+ <div :key="`expand-${index}`" class="diff-tr line_expansion old-line_expansion match">
+ <div class="diff-td text-center gl-font-regular">
+ <diff-expansion-cell
+ :file="diffFile"
+ :context-lines-path="diffFile.context_lines_path"
+ :line="line.left"
+ :is-top="index === 0"
+ :is-bottom="index + 1 === diffLinesLength"
+ :inline="inline"
+ />
+ </div>
</div>
- </div>
- <div
- v-if="line.left.rich_text"
- :key="`expand-definition-${index}`"
- class="diff-grid-row diff-tr line_holder match"
- >
- <div class="diff-grid-left diff-grid-3-col left-side">
- <div class="diff-td diff-line-num"></div>
- <div v-if="inline" class="diff-td diff-line-num"></div>
- <div
- v-safe-html="line.left.rich_text"
- class="diff-td line_content left-side gl-white-space-normal!"
- ></div>
+ <div
+ v-if="line.left.rich_text"
+ :key="`expand-definition-${index}`"
+ class="diff-grid-row diff-tr line_holder match"
+ >
+ <div class="diff-grid-left diff-grid-3-col left-side">
+ <div class="diff-td diff-line-num"></div>
+ <div v-if="inline" class="diff-td diff-line-num"></div>
+ <div
+ v-safe-html="line.left.rich_text"
+ class="diff-td line_content left-side gl-white-space-normal!"
+ ></div>
+ </div>
+ <div v-if="!inline" class="diff-grid-right diff-grid-3-col right-side">
+ <div class="diff-td diff-line-num"></div>
+ <div
+ v-safe-html="line.left.rich_text"
+ class="diff-td line_content right-side gl-white-space-normal!"
+ ></div>
+ </div>
</div>
- <div v-if="!inline" class="diff-grid-right diff-grid-3-col right-side">
- <div class="diff-td diff-line-num"></div>
- <div
- v-safe-html="line.left.rich_text"
- class="diff-td line_content right-side gl-white-space-normal!"
- ></div>
- </div>
- </div>
+ </template>
</template>
<diff-row
v-if="!line.isMatchLineLeft && !line.isMatchLineRight"
diff --git a/app/assets/javascripts/diffs/components/hidden_files_warning.vue b/app/assets/javascripts/diffs/components/hidden_files_warning.vue
index b9962682848..f6a8c679f3b 100644
--- a/app/assets/javascripts/diffs/components/hidden_files_warning.vue
+++ b/app/assets/javascripts/diffs/components/hidden_files_warning.vue
@@ -51,7 +51,7 @@ export default {
__(
'To preserve performance only %{strongStart}%{visible} of %{total}%{strongEnd} files are displayed.',
),
- { visible, total },
+ { visible, total } /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */,
)
"
>
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index 8871be1f9af..bd040cd1ba1 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -132,10 +132,10 @@ export default {
<design-note-pin
v-if="canComment && currentCommentForm"
- :position="{
+ :position="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
left: `${currentCommentForm.xPercent}%`,
top: `${currentCommentForm.yPercent}%`,
- }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
/>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index bbe27c0dbd6..6c0c9c4e1d0 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -1,6 +1,7 @@
export const INLINE_DIFF_VIEW_TYPE = 'inline';
export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match';
+export const EXPANDED_LINE_TYPE = 'expanded';
export const OLD_NO_NEW_LINE_TYPE = 'old-nonewline';
export const NEW_NO_NEW_LINE_TYPE = 'new-nonewline';
export const CONTEXT_LINE_TYPE = 'context';
@@ -101,6 +102,8 @@ export const CONFLICT_MARKER_THEIR = 'conflict_marker_their';
// Tracking events
export const DEFER_DURATION = 750;
+export const TRACKING_CAP_KEY = 'code_review_events_dispatched';
+export const TRACKING_CAP_LENGTH = 86400000; // 24 hours
export const TRACKING_CLICK_DIFF_VIEW_SETTING = 'i_code_review_click_diff_view_setting';
export const TRACKING_DIFF_VIEW_INLINE = 'i_code_review_diff_view_inline';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index fb35114c0a9..d2b798245fc 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -4,6 +4,7 @@ import {
DIFF_FILE_MANUAL_COLLAPSE,
DIFF_FILE_AUTOMATIC_COLLAPSE,
INLINE_DIFF_LINES_KEY,
+ EXPANDED_LINE_TYPE,
} from '../constants';
import * as types from './mutation_types';
import {
@@ -131,6 +132,7 @@ export default {
: line.line_code || `${fileHash}_${line.old_line}_${line.new_line}`;
return {
...line,
+ type: line.type || EXPANDED_LINE_TYPE,
line_code: lineCode,
discussions: line.discussions || [],
hasForm: false,
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index f2028892a5f..92f3cf83740 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -15,13 +15,15 @@ import {
CONFLICT_MARKER,
CONFLICT_MARKER_OUR,
CONFLICT_MARKER_THEIR,
+ EXPANDED_LINE_TYPE,
} from '../constants';
import { prepareRawDiffFile } from '../utils/diff_file';
export const isAdded = (line) => ['new', 'new-nonewline'].includes(line.type);
export const isRemoved = (line) => ['old', 'old-nonewline'].includes(line.type);
export const isUnchanged = (line) => !line.type;
-export const isMeta = (line) => ['match', 'new-nonewline', 'old-nonewline'].includes(line.type);
+export const isMeta = (line) =>
+ ['match', EXPANDED_LINE_TYPE, 'new-nonewline', 'old-nonewline'].includes(line.type);
export const isConflictMarker = (line) =>
[CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(line.type);
export const isConflictSeperator = (line) => line.type === CONFLICT_MARKER;
@@ -60,7 +62,7 @@ export const parallelizeDiffLines = (diffLines, inline) => {
const line = diffLines[i];
line.chunk = chunk;
- if (isMeta(line)) chunk += 1;
+ if (isMeta(line) && line.type !== EXPANDED_LINE_TYPE) chunk += 1;
if (isRemoved(line) || isConflictOur(line) || inline) {
lines.push({
@@ -205,7 +207,7 @@ export const findIndexInInlineLines = (lines, lineNumbers) => {
);
};
-export const getPreviousLineIndex = (diffViewType, file, lineNumbers) => {
+export const getPreviousLineIndex = (file, lineNumbers) => {
return findIndexInInlineLines(file[INLINE_DIFF_LINES_KEY], lineNumbers);
};
@@ -406,7 +408,7 @@ function deduplicateFilesList(files) {
export function prepareDiffData({ diff, priorFiles = [], meta = false }) {
const cleanedFiles = (diff.diff_files || [])
- .map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles, meta }))
+ .map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles, meta, index }))
.map(ensureBasicDiffFileLines)
.map(prepareDiffFileLines)
.map((file) => finalizeDiffFile(file));
diff --git a/app/assets/javascripts/diffs/utils/diff_file.js b/app/assets/javascripts/diffs/utils/diff_file.js
index 54dcf70c491..bcd9fa01278 100644
--- a/app/assets/javascripts/diffs/utils/diff_file.js
+++ b/app/assets/javascripts/diffs/utils/diff_file.js
@@ -50,7 +50,7 @@ function identifier(file) {
export const isNotDiffable = (file) => file?.viewer?.name === viewerModes.not_diffable;
-export function prepareRawDiffFile({ file, allFiles, meta = false }) {
+export function prepareRawDiffFile({ file, allFiles, meta = false, index = -1 }) {
const additionalProperties = {
brokenSymlink: fileSymlinkInformation(file, allFiles),
viewer: {
@@ -66,6 +66,10 @@ export function prepareRawDiffFile({ file, allFiles, meta = false }) {
additionalProperties.id = identifier(file);
}
+ if (index >= 0 && Number(index) === index) {
+ additionalProperties.order = index;
+ }
+
return Object.assign(file, additionalProperties);
}
@@ -89,6 +93,27 @@ export function getShortShaFromFile(file) {
return file.content_sha ? truncateSha(String(file.content_sha)) : null;
}
+export function match({ fileA, fileB, mode = 'universal' } = {}) {
+ const matching = {
+ universal: (a, b) => (a?.id && b?.id ? a.id === b.id : false),
+ /*
+ * MR mode can be wildly incorrect if there is ever the possibility of files from multiple MRs
+ * (e.g. a browser-local merge request/file cache).
+ * That's why the default here is "universal" mode: UUIDs can't conflict, but you can opt into
+ * the dangerous one.
+ *
+ * For reference:
+ * file_identifier_hash === sha1( `${filePath}-${Boolean(isNew)}-${Boolean(isDeleted)}-${Boolean(isRenamed)}` )
+ */
+ mr: (a, b) =>
+ a?.file_identifier_hash && b?.file_identifier_hash
+ ? a.file_identifier_hash === b.file_identifier_hash
+ : false,
+ };
+
+ return (matching[mode] || (() => false))(fileA, fileB);
+}
+
export function stats(file) {
let valid = false;
let classes = '';
diff --git a/app/assets/javascripts/diffs/utils/queue_events.js b/app/assets/javascripts/diffs/utils/queue_events.js
index 08fcc98d45f..cc7141df622 100644
--- a/app/assets/javascripts/diffs/utils/queue_events.js
+++ b/app/assets/javascripts/diffs/utils/queue_events.js
@@ -1,13 +1,31 @@
import { delay } from 'lodash';
import api from '~/api';
-import { DEFER_DURATION } from '../constants';
+import { DEFER_DURATION, TRACKING_CAP_KEY, TRACKING_CAP_LENGTH } from '../constants';
-function trackRedisHllUserEvent(event, deferDuration = 0) {
+function shouldDispatchEvent() {
+ const timestamp = parseInt(localStorage.getItem(TRACKING_CAP_KEY), 10);
+
+ if (Number.isNaN(timestamp)) {
+ return true;
+ }
+
+ return timestamp + TRACKING_CAP_LENGTH < Date.now();
+}
+
+export function dispatchRedisHllUserEvent(event, deferDuration = 0) {
delay(() => api.trackRedisHllUserEvent(event), deferDuration);
}
-export function queueRedisHllEvents(events) {
+export function queueRedisHllEvents(events, { verifyCap = false } = {}) {
+ if (verifyCap) {
+ if (!shouldDispatchEvent()) {
+ return;
+ }
+
+ localStorage.setItem(TRACKING_CAP_KEY, Date.now());
+ }
+
events.forEach((event, index) => {
- trackRedisHllUserEvent(event, DEFER_DURATION * (index + 1));
+ dispatchRedisHllUserEvent(event, DEFER_DURATION * (index + 1));
});
}
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar.vue b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
index 1427f2df461..2c177634bbe 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
@@ -55,8 +55,8 @@ export default {
id="se-toolbar"
class="gl-py-3 gl-px-5 gl-bg-white gl-border-t gl-border-b gl-display-flex gl-justify-content-space-between gl-align-items-center"
>
- <template v-for="group in $options.groups">
- <gl-button-group v-if="hasGroupItems(group)" :key="group">
+ <div v-for="group in $options.groups" :key="group">
+ <gl-button-group v-if="hasGroupItems(group)">
<template v-for="item in getGroupItems(group)">
<source-editor-toolbar-button
:key="item.id"
@@ -65,6 +65,6 @@ export default {
/>
</template>
</gl-button-group>
- </template>
+ </div>
</section>
</template>
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
index 2595d67af34..194b482c12e 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
@@ -1,7 +1,5 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import updateToolbarItemMutation from '~/editor/graphql/update_item.mutation.graphql';
-import getToolbarItemQuery from '~/editor/graphql/get_item.query.graphql';
export default {
name: 'SourceEditorToolbarButton',
@@ -20,70 +18,40 @@ export default {
},
},
},
- data() {
- return {
- buttonItem: this.button,
- };
- },
- apollo: {
- buttonItem: {
- query: getToolbarItemQuery,
- variables() {
- return {
- id: this.button.id,
- };
- },
- update({ item }) {
- return item;
- },
- skip() {
- return !this.button.id;
- },
- },
- },
computed: {
icon() {
- return this.buttonItem.selected
- ? this.buttonItem.selectedIcon || this.buttonItem.icon
- : this.buttonItem.icon;
+ return this.button.selected ? this.button.selectedIcon || this.button.icon : this.button.icon;
},
label() {
- return this.buttonItem.selected
- ? this.buttonItem.selectedLabel || this.buttonItem.label
- : this.buttonItem.label;
+ return this.button.selected
+ ? this.button.selectedLabel || this.button.label
+ : this.button.label;
+ },
+ showButton() {
+ return Object.entries(this.button).length > 0;
},
},
methods: {
clickHandler() {
- if (this.buttonItem.onClick) {
- this.buttonItem.onClick();
+ if (this.button.onClick) {
+ this.button.onClick();
}
- this.$apollo.mutate({
- mutation: updateToolbarItemMutation,
- variables: {
- id: this.buttonItem.id,
- propsToUpdate: {
- selected: !this.buttonItem.selected,
- },
- },
- });
this.$emit('click');
},
},
};
</script>
<template>
- <div>
- <gl-button
- v-gl-tooltip.hover
- :category="buttonItem.category"
- :variant="buttonItem.variant"
- type="button"
- :selected="buttonItem.selected"
- :icon="icon"
- :title="label"
- :aria-label="label"
- @click="clickHandler"
- />
- </div>
+ <gl-button
+ v-if="showButton"
+ v-gl-tooltip.hover
+ :category="button.category"
+ :variant="button.variant"
+ type="button"
+ :selected="button.selected"
+ :icon="icon"
+ :title="label"
+ :aria-label="label"
+ @click="clickHandler"
+ />
</template>
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js b/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js
new file mode 100644
index 00000000000..603ba26f22e
--- /dev/null
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js
@@ -0,0 +1,53 @@
+import produce from 'immer';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import typeDefs from '~/editor/graphql/typedefs.graphql';
+import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
+import createDefaultClient from '~/lib/graphql';
+
+Vue.use(VueApollo);
+
+const resolvers = {
+ Mutation: {
+ addToolbarItems: (_, { items = [] }, { cache }) => {
+ const itemsSourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const data = produce(itemsSourceData, (draftData) => {
+ const existingNodes = draftData?.items?.nodes || [];
+ draftData.items = {
+ nodes: Array.isArray(items) ? [...existingNodes, ...items] : [...existingNodes, items],
+ };
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+
+ removeToolbarItems: (_, { ids }, { cache }) => {
+ const sourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const {
+ items: { nodes },
+ } = sourceData;
+ const data = produce(sourceData, (draftData) => {
+ draftData.items.nodes = nodes.filter((item) => !ids.includes(item.id));
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+
+ updateToolbarItem: (_, { id, propsToUpdate }, { cache }) => {
+ const itemSourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const data = produce(itemSourceData, (draftData) => {
+ const existingNodes = draftData?.items?.nodes || [];
+ draftData.items = {
+ nodes: existingNodes.map((item) => {
+ return item.id === id ? { ...item, ...propsToUpdate } : item;
+ }),
+ };
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+ },
+};
+
+const defaultClient = createDefaultClient(resolvers, { typeDefs });
+
+export const apolloProvider = new VueApollo({
+ defaultClient,
+});
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 361122d8890..83cfdd25757 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -1,5 +1,5 @@
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
export const URI_PREFIX = 'gitlab';
export const CONTENT_UPDATE_DEBOUNCE = DEFAULT_DEBOUNCE_AND_THROTTLE_MS;
@@ -57,5 +57,8 @@ export const EXTENSION_CI_SCHEMA_FILE_NAME_MATCH = '.gitlab-ci.yml';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS = 'md';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS = 'source-editor-preview';
export const EXTENSION_MARKDOWN_PREVIEW_ACTION_ID = 'markdown-preview';
+export const EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID = 'markdown-preview-hide';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH = 0.5; // 50% of the width
export const EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY = 250; // ms
+export const EXTENSION_MARKDOWN_PREVIEW_LABEL = __('Preview Markdown');
+export const EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL = __('Hide Live Preview');
diff --git a/app/assets/javascripts/editor/extensions/source_editor_extension_base.js b/app/assets/javascripts/editor/extensions/source_editor_extension_base.js
index 3aa19df964c..0590bb7455a 100644
--- a/app/assets/javascripts/editor/extensions/source_editor_extension_base.js
+++ b/app/assets/javascripts/editor/extensions/source_editor_extension_base.js
@@ -5,7 +5,7 @@ import {
EXTENSION_BASE_LINE_NUMBERS_CLASS,
} from '../constants';
-const hashRegexp = new RegExp('#?L', 'g');
+const hashRegexp = /#?L/g;
const createAnchor = (href) => {
const fragment = new DocumentFragment();
@@ -64,7 +64,7 @@ export class SourceEditorExtension {
const [start, end] =
bounds && Array.isArray(bounds)
? bounds
- : window.location.hash?.replace(hashRegexp, '').split('-');
+ : window.location.hash.replace(hashRegexp, '').split('-');
let startLine = start ? parseInt(start, 10) : null;
let endLine = end ? parseInt(end, 10) : startLine;
if (endLine < startLine) {
diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
index 9d53268c340..11cc85c659d 100644
--- a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
+++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
@@ -3,14 +3,17 @@ import { BLOB_PREVIEW_ERROR } from '~/blob_edit/constants';
import createFlash from '~/flash';
import { sanitize } from '~/lib/dompurify';
import axios from '~/lib/utils/axios_utils';
-import { __ } from '~/locale';
import syntaxHighlight from '~/syntax_highlight';
import {
EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID,
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS,
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY,
+ EXTENSION_MARKDOWN_PREVIEW_LABEL,
+ EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+ EDITOR_TOOLBAR_RIGHT_GROUP,
} from '../constants';
const fetchPreview = (text, previewMarkdownPath) => {
@@ -41,31 +44,58 @@ export class EditorMarkdownPreviewExtension {
onSetup(instance, setupOptions) {
this.preview = {
el: undefined,
- action: undefined,
+ actions: {
+ preview: undefined,
+ hide: undefined,
+ },
shown: false,
modelChangeListener: undefined,
path: setupOptions.previewMarkdownPath,
+ actionShowPreviewCondition: instance.createContextKey('toggleLivePreview', true),
};
+ this.toolbarButtons = [];
+
this.setupPreviewAction(instance);
+ if (instance.toolbar) {
+ this.setupToolbar(instance);
+ }
+ }
- instance.getModel().onDidChangeLanguage(({ newLanguage, oldLanguage } = {}) => {
- if (newLanguage === 'markdown' && oldLanguage !== newLanguage) {
- instance.setupPreviewAction();
- } else {
- instance.cleanup();
- }
- });
+ onBeforeUnuse(instance) {
+ this.cleanup(instance);
+ const ids = this.toolbarButtons.map((item) => item.id);
+ if (instance.toolbar) {
+ instance.toolbar.removeItems(ids);
+ }
+ }
- instance.onDidChangeModel(() => {
- const model = instance.getModel();
- if (model) {
- const { language } = model.getLanguageIdentifier();
- instance.cleanup();
- if (language === 'markdown') {
- instance.setupPreviewAction();
- }
- }
- });
+ cleanup(instance) {
+ if (this.preview.modelChangeListener) {
+ this.preview.modelChangeListener.dispose();
+ }
+ this.preview.actions.preview.dispose();
+ this.preview.actions.hide.dispose();
+ if (this.preview.shown) {
+ this.togglePreviewPanel(instance);
+ this.togglePreviewLayout(instance);
+ }
+ this.preview.shown = false;
+ }
+
+ setupToolbar(instance) {
+ this.toolbarButtons = [
+ {
+ id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ label: EXTENSION_MARKDOWN_PREVIEW_LABEL,
+ icon: 'live-preview',
+ selected: false,
+ group: EDITOR_TOOLBAR_RIGHT_GROUP,
+ category: 'primary',
+ selectedLabel: EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+ onClick: () => instance.togglePreview(),
+ },
+ ];
+ instance.toolbar.addItems(this.toolbarButtons);
}
togglePreviewLayout(instance) {
@@ -103,22 +133,33 @@ export class EditorMarkdownPreviewExtension {
setupPreviewAction(instance) {
if (instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)) return;
-
- this.preview.action = instance.addAction({
- id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
- label: __('Preview Markdown'),
+ const actionBasis = {
keybindings: [
// eslint-disable-next-line no-bitwise,no-undef
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_P),
],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
-
// Method that will be executed when the action is triggered.
// @param ed The editor instance is passed in as a convenience
run(inst) {
inst.togglePreview();
},
+ };
+
+ this.preview.actions.preview = instance.addAction({
+ ...actionBasis,
+ id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ label: EXTENSION_MARKDOWN_PREVIEW_LABEL,
+
+ precondition: 'toggleLivePreview',
+ });
+ this.preview.actions.hide = instance.addAction({
+ ...actionBasis,
+ id: EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID,
+ label: EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+
+ precondition: '!toggleLivePreview',
});
}
@@ -126,18 +167,6 @@ export class EditorMarkdownPreviewExtension {
return {
markdownPreview: this.preview,
- cleanup: (instance) => {
- if (this.preview.modelChangeListener) {
- this.preview.modelChangeListener.dispose();
- }
- this.preview.action.dispose();
- if (this.preview.shown) {
- this.togglePreviewPanel(instance);
- this.togglePreviewLayout(instance);
- }
- this.preview.shown = false;
- },
-
fetchPreview: (instance) => this.fetchPreview(instance),
setupPreviewAction: (instance) => this.setupPreviewAction(instance),
@@ -149,6 +178,8 @@ export class EditorMarkdownPreviewExtension {
this.togglePreviewLayout(instance);
this.togglePreviewPanel(instance);
+ this.preview.actionShowPreviewCondition.set(!this.preview.actionShowPreviewCondition.get());
+
if (!this.preview?.shown) {
this.preview.modelChangeListener = instance.onDidChangeModelContent(
debounce(
@@ -161,6 +192,11 @@ export class EditorMarkdownPreviewExtension {
}
this.preview.shown = !this.preview?.shown;
+ if (instance.toolbar) {
+ instance.toolbar.updateItem(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID, {
+ selected: this.preview.shown,
+ });
+ }
},
};
}
diff --git a/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js b/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js
new file mode 100644
index 00000000000..9655c8ae76a
--- /dev/null
+++ b/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js
@@ -0,0 +1,98 @@
+import Vue from 'vue';
+import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
+import removeToolbarItemsMutation from '~/editor/graphql/remove_items.mutation.graphql';
+import updateToolbarItemMutation from '~/editor/graphql/update_item.mutation.graphql';
+import addToolbarItemsMutation from '~/editor/graphql/add_items.mutation.graphql';
+import SourceEditorToolbar from '~/editor/components/source_editor_toolbar.vue';
+import { apolloProvider } from '~/editor/components/source_editor_toolbar_graphql';
+
+const client = apolloProvider.defaultClient;
+
+export class ToolbarExtension {
+ /**
+ * A required getter returning the extension's name
+ * We have to provide it for every extension instead of relying on the built-in
+ * `name` prop because the prop does not survive the webpack's minification
+ * and the name mangling.
+ * @returns {string}
+ */
+ static get extensionName() {
+ return 'ToolbarExtension';
+ }
+ /**
+ * THE LIFE-CYCLE CALLBACKS
+ */
+
+ /**
+ * Is called before the extension gets used by an instance,
+ * Use `onSetup` to setup Monaco directly:
+ * actions, keystrokes, update options, etc.
+ * Is called only once before the extension gets registered
+ *
+ * @param { Object } [instance] The Source Editor instance
+ * @param { Object } [setupOptions] The setupOptions object
+ */
+ // eslint-disable-next-line class-methods-use-this
+ onSetup(instance, setupOptions) {
+ const el = setupOptions?.el || document.getElementById('editor-toolbar');
+ ToolbarExtension.setupVue(el);
+ }
+
+ static setupVue(el) {
+ client.cache.writeQuery({ query: getToolbarItemsQuery, data: { items: { nodes: [] } } });
+ const ToolbarComponent = Vue.extend(SourceEditorToolbar);
+
+ const toolbar = new ToolbarComponent({
+ el,
+ apolloProvider,
+ });
+ toolbar.$mount();
+ }
+
+ /**
+ * The public API of the extension: these are the methods that will be exposed
+ * to the end user
+ * @returns {Object}
+ */
+ // eslint-disable-next-line class-methods-use-this
+ provides() {
+ return {
+ toolbar: {
+ getItem: (id) => {
+ const items = client.readQuery({ query: getToolbarItemsQuery })?.items?.nodes || [];
+ return items.find((item) => item.id === id);
+ },
+ getAllItems: () => {
+ return client.readQuery({ query: getToolbarItemsQuery })?.items?.nodes || [];
+ },
+ addItems: (items = []) => {
+ return client.mutate({
+ mutation: addToolbarItemsMutation,
+ variables: {
+ items,
+ },
+ });
+ },
+ removeItems: (ids = []) => {
+ client.mutate({
+ mutation: removeToolbarItemsMutation,
+ variables: {
+ ids,
+ },
+ });
+ },
+ updateItem: (id = '', propsToUpdate = {}) => {
+ if (id) {
+ client.mutate({
+ mutation: updateToolbarItemMutation,
+ variables: {
+ id,
+ propsToUpdate,
+ },
+ });
+ }
+ },
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/editor/graphql/add_items.mutation.graphql b/app/assets/javascripts/editor/graphql/add_items.mutation.graphql
new file mode 100644
index 00000000000..13afcc04a48
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/add_items.mutation.graphql
@@ -0,0 +1,3 @@
+mutation addItems($items: [Item]) {
+ addToolbarItems(items: $items) @client
+}
diff --git a/app/assets/javascripts/editor/graphql/get_item.query.graphql b/app/assets/javascripts/editor/graphql/get_item.query.graphql
deleted file mode 100644
index 7c8bc09f7b0..00000000000
--- a/app/assets/javascripts/editor/graphql/get_item.query.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-query ToolbarItem($id: String!) {
- item(id: $id) @client {
- id
- label
- icon
- selected
- group
- }
-}
diff --git a/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql b/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql
new file mode 100644
index 00000000000..627f105b0ec
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql
@@ -0,0 +1,3 @@
+mutation removeToolbarItems($ids: [ID!]) {
+ removeToolbarItems(ids: $ids) @client
+}
diff --git a/app/assets/javascripts/editor/graphql/typedefs.graphql b/app/assets/javascripts/editor/graphql/typedefs.graphql
new file mode 100644
index 00000000000..2433ebf6c66
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/typedefs.graphql
@@ -0,0 +1,23 @@
+type Item {
+ id: ID!
+ label: String!
+ icon: String
+ selected: Boolean
+ group: Int!
+ category: String
+ selectedLabel: String
+}
+
+type Items {
+ nodes: [Item]!
+}
+
+extend type Query {
+ items: Items
+}
+
+extend type Mutation {
+ updateToolbarItem(id: ID!, propsToUpdate: Item!): LocalErrors
+ removeToolbarItems(ids: [ID!]): LocalErrors
+ addToolbarItems(items: [Item]): LocalErrors
+}
diff --git a/app/assets/javascripts/editor/graphql/update_item.mutation.graphql b/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
index f8424c65181..05c18988c87 100644
--- a/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
+++ b/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
@@ -1,3 +1,3 @@
-mutation updateItem($id: String!, $propsToUpdate: Item!) {
+mutation updateItem($id: ID!, $propsToUpdate: Item!) {
updateToolbarItem(id: $id, propsToUpdate: $propsToUpdate) @client
}
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index fe3229ac91b..1352211b927 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -170,23 +170,6 @@
}
]
},
- "cobertura": {
- "description": "Path for file(s) that should be parsed as Cobertura XML coverage report",
- "oneOf": [
- {
- "type": "string",
- "description": "Path to a single XML file"
- },
- {
- "type": "array",
- "description": "A list of paths to XML files that will automatically be merged into one report",
- "items": {
- "type": "string"
- },
- "minItems": 1
- }
- ]
- },
"coverage_report": {
"type": "object",
"description": "Used to collect coverage reports from the job.",
@@ -1093,8 +1076,8 @@
"description": "The name of a job to execute when the environment is about to be stopped."
},
"action": {
- "enum": ["start", "prepare", "stop"],
- "description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.",
+ "enum": ["start", "prepare", "stop", "verify", "access"],
+ "description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare'/'verify'/'access' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.",
"default": "start"
},
"auto_stop_in": {
diff --git a/app/assets/javascripts/environments/components/enable_review_app_modal.vue b/app/assets/javascripts/environments/components/enable_review_app_modal.vue
index 4d43ee156fb..6343fe8702a 100644
--- a/app/assets/javascripts/environments/components/enable_review_app_modal.vue
+++ b/app/assets/javascripts/environments/components/enable_review_app_modal.vue
@@ -45,6 +45,8 @@ export default {
copyToClipboardText: s__('EnableReviewApp|Copy snippet text'),
title: s__('ReviewApp|Enable Review App'),
},
+ visualReviewsDocs: helpPagePath('ci/review_apps/index.md', { anchor: 'visual-reviews' }),
+ connectClusterDocs: helpPagePath('user/clusters/agent/index'),
data() {
const modalInfoCopyId = uniqueId('enable-review-app-copy-string-');
@@ -64,9 +66,6 @@ export default {
except:
- ${this.defaultBranchName}`;
},
- visualReviewsDocs() {
- return helpPagePath('ci/review_apps/index.md', { anchor: 'visual-reviews' });
- },
},
};
</script>
@@ -88,11 +87,7 @@ export default {
<strong>{{ content }}</strong>
</template>
<template #link="{ content }">
- <gl-link
- href="https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html"
- target="_blank"
- >{{ content }}</gl-link
- >
+ <gl-link :href="$options.connectClusterDocs" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
@@ -134,7 +129,7 @@ export default {
<strong>{{ content }}</strong>
</template>
<template #link="{ content }">
- <gl-link :href="visualReviewsDocs" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.visualReviewsDocs" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/environments/components/environment_folder.vue b/app/assets/javascripts/environments/components/environment_folder.vue
index d5c6d26cfd0..788c3ba6fed 100644
--- a/app/assets/javascripts/environments/components/environment_folder.vue
+++ b/app/assets/javascripts/environments/components/environment_folder.vue
@@ -34,9 +34,6 @@ export default {
variables() {
return { environment: this.nestedEnvironment.latest, scope: this.scope };
},
- pollInterval() {
- return this.interval;
- },
},
interval: {
query: pollIntervalQuery,
@@ -73,6 +70,11 @@ export default {
methods: {
toggleCollapse() {
this.visible = !this.visible;
+ if (this.visible) {
+ this.$apollo.queries.folder.startPolling(this.interval);
+ } else {
+ this.$apollo.queries.folder.stopPolling();
+ }
},
isFirstEnvironment(index) {
return index === 0;
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 7b8b756487b..7fcd6e5fff8 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -146,7 +146,7 @@ export default {
{{ tableData.autoStop.title }}
</div>
</div>
- <template v-for="(model, i) in sortedEnvironments" :model="model">
+ <template v-for="(model, i) in sortedEnvironments">
<environment-item
:key="`environment-item-${i}`"
:model="model"
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 3c608ad0ba9..adb14ce3d6f 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -54,7 +54,9 @@ export default {
:key="`${tab.name}-${i}`"
:active="tab.isActive"
:title-item-class="tab.isActive ? 'gl-outline-none' : ''"
- :title-link-attributes="{ 'data-testid': `environments-tab-${tab.scope}` }"
+ :title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ 'data-testid': `environments-tab-${tab.scope}`,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@click="onChangeTab(tab.scope)"
>
<template #title>
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 3d540d46b3c..86102fd54b1 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -254,7 +254,7 @@ export default {
data-testid="integrated-disabled-alert"
@dismiss="isAlertDismissed = true"
>
- <gl-sprintf :message="this.$options.i18n.integratedErrorTrackingDisabledText">
+ <gl-sprintf :message="$options.i18n.integratedErrorTrackingDisabledText">
<template #epicLink="{ content }">
<gl-link :href="$options.epicLink" target="_blank">{{ content }}</gl-link>
</template>
diff --git a/app/assets/javascripts/error_tracking/queries/details.query.graphql b/app/assets/javascripts/error_tracking/queries/details.query.graphql
index f70e09d76f7..dd21b0f9c92 100644
--- a/app/assets/javascripts/error_tracking/queries/details.query.graphql
+++ b/app/assets/javascripts/error_tracking/queries/details.query.graphql
@@ -1,4 +1,4 @@
-query errorDetails($fullPath: ID!, $errorId: ID!) {
+query errorDetails($fullPath: ID!, $errorId: GitlabErrorTrackingDetailedErrorID!) {
project(fullPath: $fullPath) {
id
sentryErrors {
diff --git a/app/assets/javascripts/error_tracking_settings/components/app.vue b/app/assets/javascripts/error_tracking_settings/components/app.vue
index e850d954e0a..70fb1fa9cd7 100644
--- a/app/assets/javascripts/error_tracking_settings/components/app.vue
+++ b/app/assets/javascripts/error_tracking_settings/components/app.vue
@@ -152,7 +152,7 @@ export default {
<template>
<div>
<gl-alert v-if="showIntegratedTrackingDisabledAlert" variant="danger" @dismiss="dismissAlert">
- <gl-sprintf :message="this.$options.i18n.integratedErrorTrackingDisabledText">
+ <gl-sprintf :message="$options.i18n.integratedErrorTrackingDisabledText">
<template #epicLink="{ content }">
<gl-link :href="$options.epicLink" target="_blank">{{ content }}</gl-link>
</template>
diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
index 09cef74477c..b57db73a86e 100644
--- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
+++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
@@ -1,18 +1,15 @@
import { sortMilestonesByDueDate } from '~/milestones/utils';
import { mergeUrlParams } from '../lib/utils/url_utility';
-import DropdownAjaxFilter from './dropdown_ajax_filter';
import DropdownEmoji from './dropdown_emoji';
import DropdownHint from './dropdown_hint';
import DropdownNonUser from './dropdown_non_user';
import DropdownOperator from './dropdown_operator';
import DropdownUser from './dropdown_user';
import DropdownUtils from './dropdown_utils';
-import NullDropdown from './null_dropdown';
export default class AvailableDropdownMappings {
constructor({
container,
- runnerTagsEndpoint,
labelsEndpoint,
milestonesEndpoint,
releasesEndpoint,
@@ -22,7 +19,6 @@ export default class AvailableDropdownMappings {
includeDescendantGroups,
}) {
this.container = container;
- this.runnerTagsEndpoint = runnerTagsEndpoint;
this.labelsEndpoint = labelsEndpoint;
this.milestonesEndpoint = milestonesEndpoint;
this.releasesEndpoint = releasesEndpoint;
@@ -135,25 +131,6 @@ export default class AvailableDropdownMappings {
gl: DropdownNonUser,
element: this.container.querySelector('#js-dropdown-confidential'),
},
- status: {
- reference: null,
- gl: NullDropdown,
- element: this.container.querySelector('#js-dropdown-admin-runner-status'),
- },
- type: {
- reference: null,
- gl: NullDropdown,
- element: this.container.querySelector('#js-dropdown-admin-runner-type'),
- },
- tag: {
- reference: null,
- gl: DropdownAjaxFilter,
- extraArguments: {
- endpoint: this.getRunnerTagsEndpoint(),
- symbol: '~',
- },
- element: this.container.querySelector('#js-dropdown-runner-tag'),
- },
'target-branch': {
reference: null,
gl: DropdownNonUser,
@@ -202,10 +179,6 @@ export default class AvailableDropdownMappings {
return endpoint;
}
- getRunnerTagsEndpoint() {
- return `${this.runnerTagsEndpoint}.json`;
- }
-
getMergeRequestTargetBranchesEndpoint() {
const endpoint = `${
gon.relative_url_root || ''
diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js
index f8b5910de9e..e07dccd11e8 100644
--- a/app/assets/javascripts/filtered_search/constants.js
+++ b/app/assets/javascripts/filtered_search/constants.js
@@ -15,6 +15,4 @@ export const MAX_HISTORY_SIZE = 5;
export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
- ADMIN_RUNNERS: 'admin/runners',
- GROUP_RUNNERS_ANCHOR: 'runners-settings',
};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index e467e97dda9..7471d3204d6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -9,7 +9,6 @@ import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
constructor({
- runnerTagsEndpoint = '',
labelsEndpoint = '',
milestonesEndpoint = '',
iterationsEndpoint = '',
@@ -26,7 +25,6 @@ export default class FilteredSearchDropdownManager {
const removeTrailingSlash = (url) => url.replace(/\/$/, '');
this.container = FilteredSearchContainer.container;
- this.runnerTagsEndpoint = removeTrailingSlash(runnerTagsEndpoint);
this.labelsEndpoint = removeTrailingSlash(labelsEndpoint);
this.milestonesEndpoint = removeTrailingSlash(milestonesEndpoint);
this.iterationsEndpoint = removeTrailingSlash(iterationsEndpoint);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 5ba69f052c9..07f2c75f00a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -114,7 +114,6 @@ export default class FilteredSearchManager {
this.tokenizer = FilteredSearchTokenizer;
const {
- runnerTagsEndpoint = '',
labelsEndpoint = '',
milestonesEndpoint = '',
releasesEndpoint = '',
@@ -124,7 +123,6 @@ export default class FilteredSearchManager {
} = this.filteredSearchInput.dataset;
this.dropdownManager = new FilteredSearchDropdownManager({
- runnerTagsEndpoint,
labelsEndpoint,
milestonesEndpoint,
releasesEndpoint,
@@ -583,7 +581,7 @@ export default class FilteredSearchManager {
* Eg. not[foo]=%bar
* key = foo; value = %bar
*/
- const notKeyValueRegex = new RegExp(/not\[(\w+)\]\[?\]?=(.*)/);
+ const notKeyValueRegex = /not\[(\w+)\]\[?\]?=(.*)/;
return params.map((query) => {
// Check if there are matches for `not` operator
diff --git a/app/assets/javascripts/filtered_search/group_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/group_runners_filtered_search_token_keys.js
deleted file mode 100644
index ceeb71c4eec..00000000000
--- a/app/assets/javascripts/filtered_search/group_runners_filtered_search_token_keys.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { __ } from '~/locale';
-import FilteredSearchTokenKeys from './filtered_search_token_keys';
-
-const tokenKeys = [
- {
- formattedKey: __('Status'),
- key: 'status',
- type: 'string',
- param: 'status',
- symbol: '',
- icon: 'messages',
- tag: 'status',
- },
- {
- formattedKey: __('Type'),
- key: 'type',
- type: 'string',
- param: 'type',
- symbol: '',
- icon: 'cube',
- tag: 'type',
- },
-];
-
-const GroupRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
-
-export default GroupRunnersFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/null_dropdown.js b/app/assets/javascripts/filtered_search/null_dropdown.js
deleted file mode 100644
index 4cfce2a5beb..00000000000
--- a/app/assets/javascripts/filtered_search/null_dropdown.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import FilteredSearchDropdown from './filtered_search_dropdown';
-
-export default class NullDropdown extends FilteredSearchDropdown {
- renderContent(forceShowList = false) {
- this.droplab.changeHookList(this.hookId, this.dropdown, [], this.config);
-
- super.renderContent(forceShowList);
- }
-}
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 24ec16bf20e..5a47e76d597 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -86,48 +86,43 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => {
/**
* Render an alert at the top of the page, or, optionally an
- * arbitrary existing container.
- *
- * This alert is always dismissible.
- *
- * Usage:
- *
- * 1. Render a new alert
+ * arbitrary existing container. This alert is always dismissible.
*
+ * @example
+ * // Render a new alert
* import { createAlert, VARIANT_WARNING } from '~/flash';
*
* createAlert({ message: 'My error message' });
* createAlert({ message: 'My warning message', variant: VARIANT_WARNING });
*
- * 2. Dismiss this alert programmatically
- *
+ * @example
+ * // Dismiss this alert programmatically
* const alert = createAlert({ message: 'Message' });
*
* // ...
*
* alert.dismiss();
*
- * 3. Respond to the alert being dismissed
+ * @example
+ * // Respond to the alert being dismissed
+ * createAlert({ message: 'Message', onDismiss: () => {} });
*
- * createAlert({ message: 'Message', onDismiss: () => { ... }});
- *
- * @param {Object} options Options to control the flash message
- * @param {String} options.message Alert message text
- * @param {String?} options.variant Which GlAlert variant to use, should be VARIANT_SUCCESS, VARIANT_WARNING, VARIANT_DANGER, VARIANT_INFO or VARIANT_TIP. Defaults to VARIANT_DANGER.
- * @param {Object?} options.parent Reference to parent element under which alert needs to appear. Defaults to `document`.
- * @param {Function?} options.onDismiss Handler to call when this alert is dismissed.
- * @param {Object?} options.containerSelector Selector for the container of the alert
- * @param {Object?} options.primaryButton Object describing primary button of alert
- * @param {String?} link Href of primary button
- * @param {String?} text Text of primary button
- * @param {Function?} clickHandler Handler to call when primary button is clicked on. The click event is sent as an argument.
- * @param {Object?} options.secondaryButton Object describing secondary button of alert
- * @param {String?} link Href of secondary button
- * @param {String?} text Text of secondary button
- * @param {Function?} clickHandler Handler to call when secondary button is clicked on. The click event is sent as an argument.
- * @param {Boolean?} options.captureError Whether to send error to Sentry
- * @param {Object} options.error Error to be captured in Sentry
- * @returns
+ * @param {object} options - Options to control the flash message
+ * @param {string} options.message - Alert message text
+ * @param {VARIANT_SUCCESS|VARIANT_WARNING|VARIANT_DANGER|VARIANT_INFO|VARIANT_TIP} [options.variant] - Which GlAlert variant to use; it defaults to VARIANT_DANGER.
+ * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`.
+ * @param {Function} [options.onDismiss] - Handler to call when this alert is dismissed.
+ * @param {string} [options.containerSelector] - Selector for the container of the alert
+ * @param {object} [options.primaryButton] - Object describing primary button of alert
+ * @param {string} [options.primaryButton.link] - Href of primary button
+ * @param {string} [options.primaryButton.text] - Text of primary button
+ * @param {Function} [options.primaryButton.clickHandler] - Handler to call when primary button is clicked on. The click event is sent as an argument.
+ * @param {object} [options.secondaryButton] - Object describing secondary button of alert
+ * @param {string} [options.secondaryButton.link] - Href of secondary button
+ * @param {string} [options.secondaryButton.text] - Text of secondary button
+ * @param {Function} [options.secondaryButton.clickHandler] - Handler to call when secondary button is clicked on. The click event is sent as an argument.
+ * @param {boolean} [options.captureError] - Whether to send error to Sentry
+ * @param {object} [options.error] - Error to be captured in Sentry
*/
const createAlert = function createAlert({
message,
@@ -207,22 +202,25 @@ const createAlert = function createAlert({
});
};
-/*
- * Flash banner supports different types of Flash configurations
- * along with ability to provide actionConfig which can be used to show
- * additional action or link on banner next to message
+/**
+ * @deprecated use `createAlert` instead
+ *
+ * Flash banner supports different types of Flash configurations
+ * along with ability to provide actionConfig which can be used to show
+ * additional action or link on banner next to message
*
- * @param {Object} options Options to control the flash message
- * @param {String} options.message Flash message text
- * @param {String} options.type Type of Flash, it can be `notice`, `success`, `warning` or `alert` (default)
- * @param {Object} options.parent Reference to parent element under which Flash needs to appear
- * @param {Object} options.actionConfig Map of config to show action on banner
- * @param {String} href URL to which action config should point to (default: '#')
- * @param {String} title Title of action
- * @param {Function} clickHandler Method to call when action is clicked on
- * @param {Boolean} options.fadeTransition Boolean to determine whether to fade the alert out
- * @param {Boolean} options.captureError Boolean to determine whether to send error to Sentry
- * @param {Object} options.error Error to be captured in Sentry
+ * @param {object} options - Options to control the flash message
+ * @param {string} options.message - Flash message text
+ * @param {'alert'|'notice'|'success'|'warning'} [options.type] - Type of Flash; it defaults to 'alert'
+ * @param {Element|Document} [options.parent] - Reference to parent element under which Flash needs to appear
+ * @param {object} [options.actionConfig] - Map of config to show action on banner
+ * @param {string} [options.actionConfig.href] - URL to which action config should point to (default: '#')
+ * @param {string} [options.actionConfig.title] - Title of action
+ * @param {Function} [options.actionConfig.clickHandler] - Method to call when action is clicked on
+ * @param {boolean} [options.fadeTransition] - Boolean to determine whether to fade the alert out
+ * @param {boolean} [options.addBodyClass] - Adds `flash-shown` class to the `body` element
+ * @param {boolean} [options.captureError] - Boolean to determine whether to send error to Sentry
+ * @param {object} [options.error] - Error to be captured in Sentry
*/
const createFlash = function createFlash({
message,
diff --git a/app/assets/javascripts/frequent_items/constants.js b/app/assets/javascripts/frequent_items/constants.js
index 9e1dcf70aa5..cb5d21161a9 100644
--- a/app/assets/javascripts/frequent_items/constants.js
+++ b/app/assets/javascripts/frequent_items/constants.js
@@ -7,7 +7,7 @@ export const FREQUENT_ITEMS = {
ELIGIBLE_FREQUENCY: 3,
};
-export const HOUR_IN_MS = 3600000;
+export const FIFTEEN_MINUTES_IN_MS = 900000;
export const STORAGE_KEY = {
projects: 'frequent-projects',
diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js
index 27ef47df8c8..1c33c8b1084 100644
--- a/app/assets/javascripts/frequent_items/utils.js
+++ b/app/assets/javascripts/frequent_items/utils.js
@@ -1,7 +1,7 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { take } from 'lodash';
import { sanitize } from '~/lib/dompurify';
-import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
+import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from './constants';
export const isMobile = () => ['md', 'sm', 'xs'].includes(bp.getBreakpointSize());
@@ -38,7 +38,8 @@ export const updateExistingFrequentItem = (frequentItem, item) => {
// `frequentItem` comes from localStorage and it's possible it doesn't have a `lastAccessedOn`
const neverAccessed = !frequentItem.lastAccessedOn;
const shouldUpdate =
- neverAccessed || Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / HOUR_IN_MS > 1;
+ neverAccessed ||
+ Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / FIFTEEN_MINUTES_IN_MS > 1;
return {
...item,
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 8cb2e9e249b..b1af1ad797b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -844,7 +844,7 @@ class GfmAutoComplete {
}
}
-GfmAutoComplete.regexSubtext = new RegExp(/\s+/g);
+GfmAutoComplete.regexSubtext = /\s+/g;
GfmAutoComplete.defaultLoadingData = ['loading'];
diff --git a/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
index 78b2cd34a5c..824997f8e33 100644
--- a/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
+++ b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
@@ -1,4 +1,5 @@
fragment TimelogFragment on Timelog {
+ id
timeSpent
user {
id
diff --git a/app/assets/javascripts/graphql_shared/fragments/user_availability.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/user_availability.fragment.graphql
index 429993b37bf..0b451262b5a 100644
--- a/app/assets/javascripts/graphql_shared/fragments/user_availability.fragment.graphql
+++ b/app/assets/javascripts/graphql_shared/fragments/user_availability.fragment.graphql
@@ -1,4 +1,3 @@
-# eslint-disable-next-line @graphql-eslint/require-id-when-available
fragment UserAvailability on User {
status {
availability
diff --git a/app/assets/javascripts/graphql_shared/mutations/todo_mark_done.mutation.graphql b/app/assets/javascripts/graphql_shared/mutations/todo_mark_done.mutation.graphql
index 4d59b4d94cd..e8ad919f4a8 100644
--- a/app/assets/javascripts/graphql_shared/mutations/todo_mark_done.mutation.graphql
+++ b/app/assets/javascripts/graphql_shared/mutations/todo_mark_done.mutation.graphql
@@ -1,4 +1,4 @@
-mutation todoMarkDone($id: ID!) {
+mutation todoMarkDone($id: TodoID!) {
todoMarkDone(input: { id: $id }) {
errors
todo {
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 3d6360fc4f8..7ca3f20ec1c 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -76,6 +76,10 @@
"Discussion",
"Note"
],
+ "SecurityPolicySource": [
+ "GroupSecurityPolicySource",
+ "ProjectSecurityPolicySource"
+ ],
"Service": [
"BaseService",
"JiraService"
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index b6a6720e7a1..49e7dd28ff6 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -1,8 +1,13 @@
+import { debounce } from 'lodash';
+
import createFlash from '~/flash';
import { __ } from '~/locale';
import { getGroupPathAvailability } from '~/rest_api';
+import axios from '~/lib/utils/axios_utils';
import { slugify } from './lib/utils/text_utility';
+const DEBOUNCE_TIMEOUT_DURATION = 1000;
+
export default class Group {
constructor() {
this.groupPaths = Array.from(document.querySelectorAll('.js-autofill-group-path'));
@@ -10,7 +15,11 @@ export default class Group {
this.parentId = document.getElementById('group_parent_id');
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
- this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this);
+ this.updateGroupPathSlugHandler = debounce(
+ this.updateGroupPathSlug.bind(this),
+ DEBOUNCE_TIMEOUT_DURATION,
+ );
+ this.currentApiRequestController = null;
this.groupNames.forEach((groupName) => {
groupName.addEventListener('keyup', this.updateHandler);
@@ -44,13 +53,23 @@ export default class Group {
});
}
- updateGroupPathSlug({ currentTarget: { value } = '' } = {}) {
- const slug = this.groupPaths[0]?.value || slugify(value);
+ updateGroupPathSlug({ target: { value } = '' } = {}) {
+ if (this.currentApiRequestController !== null) {
+ this.currentApiRequestController.abort();
+ }
+
+ this.currentApiRequestController = new AbortController();
+
+ const slug = slugify(value);
if (!slug) return;
- getGroupPathAvailability(slug, this.parentId?.value)
+ getGroupPathAvailability(slug, this.parentId?.value, {
+ signal: this.currentApiRequestController.signal,
+ })
.then(({ data }) => data)
.then(({ exists, suggests }) => {
+ this.currentApiRequestController = null;
+
if (exists && suggests.length) {
const [suggestedSlug] = suggests;
@@ -63,10 +82,14 @@ export default class Group {
});
}
})
- .catch(() =>
+ .catch((error) => {
+ if (axios.isCancel(error)) {
+ return;
+ }
+
createFlash({
message: __('An error occurred while checking group path. Please refresh and try again.'),
- }),
- );
+ });
+ });
}
}
diff --git a/app/assets/javascripts/group_settings/components/shared_runners_form.vue b/app/assets/javascripts/group_settings/components/shared_runners_form.vue
index dcac337c6ef..3365f4aa76c 100644
--- a/app/assets/javascripts/group_settings/components/shared_runners_form.vue
+++ b/app/assets/javascripts/group_settings/components/shared_runners_form.vue
@@ -1,73 +1,64 @@
<script>
-import { GlToggle, GlLoadingIcon, GlTooltip, GlAlert } from '@gitlab/ui';
-import { debounce } from 'lodash';
+import { GlToggle, GlAlert } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
-import { DEBOUNCE_TOGGLE_DELAY, ERROR_MESSAGE } from '../constants';
+import { ERROR_MESSAGE } from '../constants';
export default {
components: {
GlToggle,
- GlLoadingIcon,
- GlTooltip,
GlAlert,
},
inject: [
'updatePath',
- 'sharedRunnersAvailability',
- 'parentSharedRunnersAvailability',
- 'runnerEnabled',
- 'runnerDisabled',
- 'runnerAllowOverride',
+ 'sharedRunnersSetting',
+ 'parentSharedRunnersSetting',
+ 'runnerEnabledValue',
+ 'runnerDisabledValue',
+ 'runnerAllowOverrideValue',
],
data() {
return {
isLoading: false,
- enabled: true,
- allowOverride: false,
+ value: this.sharedRunnersSetting,
error: null,
};
},
computed: {
- toggleDisabled() {
- return this.parentSharedRunnersAvailability === this.runnerDisabled || this.isLoading;
+ isSharedRunnersToggleDisabled() {
+ return this.parentSharedRunnersSetting === this.runnerDisabledValue;
},
- enabledOrDisabledSetting() {
- return this.enabled ? this.runnerEnabled : this.runnerDisabled;
+ sharedRunnersToggleValue() {
+ return this.value === this.runnerEnabledValue;
},
- disabledWithOverrideSetting() {
- return this.allowOverride ? this.runnerAllowOverride : this.runnerDisabled;
+ isOverrideToggleDisabled() {
+ // cannot override when sharing is enabled
+ return this.isSharedRunnersToggleDisabled || this.value === this.runnerEnabledValue;
+ },
+ overrideToggleValue() {
+ return this.value === this.runnerAllowOverrideValue;
},
- },
- created() {
- if (this.sharedRunnersAvailability !== this.runnerEnabled) {
- this.enabled = false;
- }
-
- if (this.sharedRunnersAvailability === this.runnerAllowOverride) {
- this.allowOverride = true;
- }
},
methods: {
- generatePayload(data) {
- return { shared_runners_setting: data };
- },
- enableOrDisable() {
- this.updateRunnerSettings(this.generatePayload(this.enabledOrDisabledSetting));
-
- // reset override toggle to false if shared runners are enabled
- this.allowOverride = false;
+ onSharedRunnersToggle(value) {
+ const newSetting = value ? this.runnerEnabledValue : this.runnerDisabledValue;
+ this.updateSetting(newSetting);
},
- override() {
- this.updateRunnerSettings(this.generatePayload(this.disabledWithOverrideSetting));
+ onOverrideToggle(value) {
+ const newSetting = value ? this.runnerAllowOverrideValue : this.runnerDisabledValue;
+ this.updateSetting(newSetting);
},
- updateRunnerSettings: debounce(function debouncedUpdateRunnerSettings(setting) {
+ updateSetting(setting) {
+ if (this.isLoading) {
+ return;
+ }
+
this.isLoading = true;
axios
- .put(this.updatePath, setting)
+ .put(this.updatePath, { shared_runners_setting: setting })
.then(() => {
- this.isLoading = false;
+ this.value = setting;
})
.catch((error) => {
const message = [
@@ -76,51 +67,52 @@ export default {
].join(' ');
this.error = message;
+ })
+ .finally(() => {
+ this.isLoading = false;
});
- }, DEBOUNCE_TOGGLE_DELAY),
+ },
},
};
</script>
<template>
- <div ref="sharedRunnersForm">
- <gl-alert v-if="error" variant="danger" :dismissible="false">{{ error }}</gl-alert>
+ <div>
+ <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5">
+ {{ error }}
+ </gl-alert>
- <h4 class="gl-display-flex gl-align-items-center">
- {{ __('Set up shared runner availability') }}
- <gl-loading-icon v-if="isLoading" class="gl-ml-3" size="sm" inline />
- </h4>
+ <gl-alert
+ v-if="isSharedRunnersToggleDisabled"
+ variant="warning"
+ :dismissible="false"
+ class="gl-mb-5"
+ >
+ {{ __('Shared runners are disabled for the parent group') }}
+ </gl-alert>
- <section class="gl-mt-5">
+ <section class="gl-mb-5">
<gl-toggle
- v-model="enabled"
- :disabled="toggleDisabled"
+ :value="sharedRunnersToggleValue"
+ :is-loading="isLoading"
+ :disabled="isSharedRunnersToggleDisabled"
:label="__('Enable shared runners for this group')"
- data-testid="enable-runners-toggle"
- @change="enableOrDisable"
+ :help="__('Enable shared runners for all projects and subgroups in this group.')"
+ data-testid="shared-runners-toggle"
+ @change="onSharedRunnersToggle"
/>
-
- <span class="gl-text-gray-600">
- {{ __('Enable shared runners for all projects and subgroups in this group.') }}
- </span>
</section>
- <section v-if="!enabled" class="gl-mt-5">
+ <section class="gl-mb-5">
<gl-toggle
- v-model="allowOverride"
- :disabled="toggleDisabled"
+ :value="overrideToggleValue"
+ :is-loading="isLoading"
+ :disabled="isOverrideToggleDisabled"
:label="__('Allow projects and subgroups to override the group setting')"
+ :help="__('Allows projects or subgroups in this group to override the global setting.')"
data-testid="override-runners-toggle"
- @change="override"
+ @change="onOverrideToggle"
/>
-
- <span class="gl-text-gray-600">
- {{ __('Allows projects or subgroups in this group to override the global setting.') }}
- </span>
</section>
-
- <gl-tooltip v-if="toggleDisabled" :target="() => $refs.sharedRunnersForm">
- {{ __('Shared runners are disabled for the parent group') }}
- </gl-tooltip>
</div>
</template>
diff --git a/app/assets/javascripts/group_settings/constants.js b/app/assets/javascripts/group_settings/constants.js
index 4067b6b52a3..ab5c0db45ba 100644
--- a/app/assets/javascripts/group_settings/constants.js
+++ b/app/assets/javascripts/group_settings/constants.js
@@ -1,6 +1,3 @@
import { __ } from '~/locale';
-// Debounce delay in milliseconds
-export const DEBOUNCE_TOGGLE_DELAY = 1000;
-
export const ERROR_MESSAGE = __('Refresh the page and try again.');
diff --git a/app/assets/javascripts/group_settings/mount_shared_runners.js b/app/assets/javascripts/group_settings/mount_shared_runners.js
index 21a2373e2b1..aeb6d57a11a 100644
--- a/app/assets/javascripts/group_settings/mount_shared_runners.js
+++ b/app/assets/javascripts/group_settings/mount_shared_runners.js
@@ -6,22 +6,22 @@ export default (containerId = 'update-shared-runners-form') => {
const {
updatePath,
- sharedRunnersAvailability,
- parentSharedRunnersAvailability,
- runnerEnabled,
- runnerDisabled,
- runnerAllowOverride,
+ sharedRunnersSetting,
+ parentSharedRunnersSetting,
+ runnerEnabledValue,
+ runnerDisabledValue,
+ runnerAllowOverrideValue,
} = containerEl.dataset;
return new Vue({
el: containerEl,
provide: {
updatePath,
- sharedRunnersAvailability,
- parentSharedRunnersAvailability,
- runnerEnabled,
- runnerDisabled,
- runnerAllowOverride,
+ sharedRunnersSetting,
+ parentSharedRunnersSetting,
+ runnerEnabledValue,
+ runnerDisabledValue,
+ runnerAllowOverrideValue,
},
render(createElement) {
return createElement(UpdateSharedRunnersForm);
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index 4406cacdf3f..adf304aebc7 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -120,9 +120,11 @@ export default {
...mapActions(['setSearch', 'fetchAutocompleteOptions', 'clearAutocomplete']),
openDropdown() {
this.showDropdown = true;
+ this.$emit('toggleDropdown', this.showDropdown);
},
closeDropdown() {
this.showDropdown = false;
+ this.$emit('toggleDropdown', this.showDropdown);
},
submitSearch() {
return visitUrl(this.currentFocusedOption?.url || this.searchQuery);
@@ -146,7 +148,7 @@ export default {
v-outside="closeDropdown"
role="search"
:aria-label="$options.i18n.searchGitlab"
- class="header-search gl-relative gl-rounded-base"
+ class="header-search gl-relative gl-rounded-base gl-w-full"
:class="headerSearchActivityDescriptor"
>
<gl-search-box-by-type
diff --git a/app/assets/javascripts/header_search/index.js b/app/assets/javascripts/header_search/index.js
index 4af8513ecdb..b2c505d569f 100644
--- a/app/assets/javascripts/header_search/index.js
+++ b/app/assets/javascripts/header_search/index.js
@@ -7,6 +7,7 @@ Vue.use(Translate);
export const initHeaderSearchApp = (search = '') => {
const el = document.getElementById('js-header-search');
+ let navBarEl = null;
if (!el) {
return false;
@@ -19,8 +20,21 @@ export const initHeaderSearchApp = (search = '') => {
return new Vue({
el,
store: createStore({ searchPath, issuesPath, mrPath, autocompletePath, searchContext, search }),
+ mounted() {
+ navBarEl = document.querySelector('.header-content');
+ },
render(createElement) {
- return createElement(HeaderSearchApp);
+ return createElement(HeaderSearchApp, {
+ on: {
+ toggleDropdown: (isVisible = false) => {
+ if (isVisible) {
+ navBarEl?.classList.add('header-search-is-active');
+ } else {
+ navBarEl?.classList.remove('header-search-is-active');
+ }
+ },
+ },
+ });
},
});
};
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 38f3b094b7c..cb906374fe1 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -216,7 +216,9 @@ export default {
modal-id="ide-commit-error-modal"
:title="lastCommitError.title"
:action-primary="commitErrorPrimaryAction.button"
- :action-cancel="{ text: __('Cancel') }"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: __('Cancel'),
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@ok="commitErrorPrimaryAction.callback"
>
<div v-safe-html="lastCommitError.messageHTML"></div>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 32f87cb0a92..93ff7e8566f 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -108,7 +108,7 @@ export default {
by
<user-avatar-image
css-classes="ide-status-avatar"
- :size="18"
+ :size="16"
:img-src="latestPipeline && latestPipeline.commit.author_gravatar_url"
:img-alt="lastCommit.author_name"
:tooltip-text="lastCommit.author_name"
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue
index 9262a4e1e95..5455a034106 100644
--- a/app/assets/javascripts/import_entities/components/import_status.vue
+++ b/app/assets/javascripts/import_entities/components/import_status.vue
@@ -44,6 +44,11 @@ const STATUS_MAP = {
text: __('Failed'),
variant: 'danger',
},
+ [STATUSES.TIMEOUT]: {
+ icon: 'status-failed',
+ text: __('Timeout'),
+ variant: 'danger',
+ },
[STATUSES.CANCELLED]: {
icon: 'status-stopped',
text: __('Cancelled'),
diff --git a/app/assets/javascripts/import_entities/constants.js b/app/assets/javascripts/import_entities/constants.js
index 20a4d2d84b4..c470da21765 100644
--- a/app/assets/javascripts/import_entities/constants.js
+++ b/app/assets/javascripts/import_entities/constants.js
@@ -10,4 +10,5 @@ export const STATUSES = {
NONE: 'none',
SCHEDULING: 'scheduling',
CANCELLED: 'cancelled',
+ TIMEOUT: 'timeout',
};
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index 028197ec9b1..ce401862cc1 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -25,7 +25,7 @@ import importGroupsMutation from '../graphql/mutations/import_groups.mutation.gr
import updateImportStatusMutation from '../graphql/mutations/update_import_status.mutation.graphql';
import availableNamespacesQuery from '../graphql/queries/available_namespaces.query.graphql';
import bulkImportSourceGroupsQuery from '../graphql/queries/bulk_import_source_groups.query.graphql';
-import { NEW_NAME_FIELD, i18n } from '../constants';
+import { NEW_NAME_FIELD, ROOT_NAMESPACE, i18n } from '../constants';
import { StatusPoller } from '../services/status_poller';
import { isFinished, isAvailableForImport, isNameValid, isSameTarget } from '../utils';
import ImportActionsCell from './import_actions_cell.vue';
@@ -71,6 +71,10 @@ export default {
type: String,
required: true,
},
+ historyPath: {
+ type: String,
+ required: true,
+ },
},
data() {
@@ -426,10 +430,10 @@ export default {
return this.importTargets[group.id];
}
- const defaultTargetNamespace = this.availableNamespaces[0] ?? { fullPath: '', id: null };
+ const defaultTargetNamespace = this.availableNamespaces[0] ?? ROOT_NAMESPACE;
let importTarget;
if (group.lastImportTarget) {
- const targetNamespace = this.availableNamespaces.find(
+ const targetNamespace = [ROOT_NAMESPACE, ...this.availableNamespaces].find(
(ns) => ns.fullPath === group.lastImportTarget.targetNamespace,
);
@@ -485,12 +489,15 @@ export default {
<template>
<div>
- <h1
- class="gl-my-0 gl-py-4 gl-font-size-h1 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex"
+ <div
+ class="gl-display-flex gl-align-items-center gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1"
>
- <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
- {{ s__('BulkImport|Import groups from GitLab') }}
- </h1>
+ <h1 class="gl-my-0 gl-py-4 gl-font-size-h1gl-display-flex">
+ <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
+ {{ s__('BulkImport|Import groups from GitLab') }}
+ </h1>
+ <gl-link :href="historyPath" class="gl-ml-auto">{{ s__('BulkImport|History') }}</gl-link>
+ </div>
<gl-alert
v-if="unavailableFeatures.length > 0 && unavailableFeaturesAlertVisible"
variant="warning"
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
index 344a6e45370..4fbbd5b239c 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
@@ -57,6 +57,7 @@ export default {
toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
class="gl-h-7 gl-flex-grow-1"
data-qa-selector="target_namespace_selector_dropdown"
+ data-testid="target-namespace-selector"
>
<gl-dropdown-item @click="$emit('update-target-namespace', { fullPath: '', id: null })">{{
s__('BulkImport|No parent')
diff --git a/app/assets/javascripts/import_entities/import_groups/constants.js b/app/assets/javascripts/import_entities/import_groups/constants.js
index ac1466238d0..32137308684 100644
--- a/app/assets/javascripts/import_entities/import_groups/constants.js
+++ b/app/assets/javascripts/import_entities/import_groups/constants.js
@@ -18,3 +18,5 @@ export const i18n = {
};
export const NEW_NAME_FIELD = 'newName';
+
+export const ROOT_NAMESPACE = { fullPath: '', id: null };
diff --git a/app/assets/javascripts/import_entities/import_groups/index.js b/app/assets/javascripts/import_entities/import_groups/index.js
index 67a7258d504..02af0db7f9a 100644
--- a/app/assets/javascripts/import_entities/import_groups/index.js
+++ b/app/assets/javascripts/import_entities/import_groups/index.js
@@ -17,6 +17,7 @@ export function mountImportGroupsApp(mountElement) {
jobsPath,
sourceUrl,
groupPathRegex,
+ historyPath,
} = mountElement.dataset;
const apolloProvider = new VueApollo({
defaultClient: createApolloClient({
@@ -38,6 +39,7 @@ export function mountImportGroupsApp(mountElement) {
sourceUrl,
jobsPath,
groupPathRegex: new RegExp(`^(${groupPathRegex})$`),
+ historyPath,
},
});
},
diff --git a/app/assets/javascripts/import_entities/import_groups/utils.js b/app/assets/javascripts/import_entities/import_groups/utils.js
index 1d0ab75e1cb..f896203ba60 100644
--- a/app/assets/javascripts/import_entities/import_groups/utils.js
+++ b/app/assets/javascripts/import_entities/import_groups/utils.js
@@ -10,7 +10,7 @@ export function getInvalidNameValidationMessage(importTarget) {
}
export function isFinished(group) {
- return [STATUSES.FINISHED, STATUSES.FAILED].includes(group.progress?.status);
+ return [STATUSES.FINISHED, STATUSES.FAILED, STATUSES.TIMEOUT].includes(group.progress?.status);
}
export function isAvailableForImport(group) {
diff --git a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
index e0703a77424..0307607321e 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
@@ -1,5 +1,11 @@
<script>
-import { GlButton, GlLoadingIcon, GlIntersectionObserver, GlModal, GlFormInput } from '@gitlab/ui';
+import {
+ GlButton,
+ GlLoadingIcon,
+ GlIntersectionObserver,
+ GlModal,
+ GlSearchBoxByClick,
+} from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { n__, __, sprintf } from '~/locale';
import ProviderRepoTableRow from './provider_repo_table_row.vue';
@@ -12,7 +18,7 @@ export default {
GlButton,
GlModal,
GlIntersectionObserver,
- GlFormInput,
+ GlSearchBoxByClick,
},
props: {
providerTitle: {
@@ -134,13 +140,13 @@ export default {
<slot name="actions"></slot>
<form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
- <gl-form-input
+ <gl-search-box-by-click
data-qa-selector="githubish_import_filter_field"
name="filter"
:placeholder="__('Filter by name')"
autofocus
- size="lg"
- @keyup.enter="setFilter($event.target.value)"
+ @submit="setFilter"
+ @clear="setFilter('')"
/>
</form>
</div>
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index bfc5bd823a2..922e870caa7 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -338,7 +338,9 @@ export default {
:show-items="showList"
:show-error-msg="showErrorMsg"
:i18n="$options.i18n"
- :items="incidents.list || []"
+ :items="
+ incidents.list || [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
+ "
:page-info="incidents.pageInfo"
:items-count="incidentsCount"
:status-tabs="$options.statusTabs"
@@ -372,7 +374,10 @@ export default {
<template #table>
<gl-table
- :items="incidents.list || []"
+ :items="
+ incidents.list ||
+ [] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */
+ "
:fields="availableFields"
:busy="loading"
stacked="md"
diff --git a/app/assets/javascripts/incidents/graphql/fragments/incident_fields.fragment.graphql b/app/assets/javascripts/incidents/graphql/fragments/incident_fields.fragment.graphql
index b72941966c6..2d84b141f32 100644
--- a/app/assets/javascripts/incidents/graphql/fragments/incident_fields.fragment.graphql
+++ b/app/assets/javascripts/incidents/graphql/fragments/incident_fields.fragment.graphql
@@ -1,4 +1,3 @@
-# eslint-disable-next-line @graphql-eslint/require-id-when-available
fragment IncidentFields on Issue {
severity
escalationStatus
diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js
index c5ed5bb08a9..b9975eed716 100644
--- a/app/assets/javascripts/integrations/constants.js
+++ b/app/assets/javascripts/integrations/constants.js
@@ -37,3 +37,13 @@ export const integrationFormSectionComponents = {
[integrationFormSections.JIRA_TRIGGER]: 'IntegrationSectionJiraTrigger',
[integrationFormSections.JIRA_ISSUES]: 'IntegrationSectionJiraIssues',
};
+
+export const billingPlans = {
+ PREMIUM: 'premium',
+ ULTIMATE: 'ultimate',
+};
+
+export const billingPlanNames = {
+ [billingPlans.PREMIUM]: s__('BillingPlans|Premium'),
+ [billingPlans.ULTIMATE]: s__('BillingPlans|Ultimate'),
+};
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 661299920c7..9f43360fb73 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -1,5 +1,11 @@
<script>
-import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml, GlForm } from '@gitlab/ui';
+import {
+ GlBadge,
+ GlButton,
+ GlModalDirective,
+ GlSafeHtmlDirective as SafeHtml,
+ GlForm,
+} from '@gitlab/ui';
import axios from 'axios';
import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
@@ -10,6 +16,7 @@ import {
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
integrationLevels,
integrationFormSectionComponents,
+ billingPlanNames,
} from '~/integrations/constants';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import csrf from '~/lib/utils/csrf';
@@ -42,6 +49,7 @@ export default {
import(
/* webpackChunkName: 'integrationSectionJiraTrigger' */ '~/integrations/edit/components/sections/jira_trigger.vue'
),
+ GlBadge,
GlButton,
GlForm,
},
@@ -177,6 +185,7 @@ export default {
},
csrf,
integrationFormSectionComponents,
+ billingPlanNames,
};
</script>
@@ -214,7 +223,20 @@ export default {
>
<div class="row">
<div class="col-lg-4">
- <h4 class="gl-mt-0">{{ section.title }}</h4>
+ <h4 class="gl-mt-0">
+ {{ section.title
+ }}<gl-badge
+ v-if="section.plan"
+ :href="propsSource.aboutPricingUrl"
+ target="_blank"
+ rel="noopener noreferrer"
+ variant="tier"
+ icon="license"
+ class="gl-ml-3"
+ >
+ {{ $options.billingPlanNames[section.plan] }}
+ </gl-badge>
+ </h4>
<p v-safe-html:[$options.descriptionHtmlConfig]="section.description"></p>
</div>
diff --git a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
index f00339c92fa..584d23e17e1 100644
--- a/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
+++ b/app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue
@@ -2,7 +2,6 @@
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { s__, __ } from '~/locale';
-import JiraUpgradeCta from './jira_upgrade_cta.vue';
export default {
name: 'JiraIssuesFields',
@@ -10,7 +9,6 @@ export default {
GlFormGroup,
GlFormCheckbox,
GlFormInput,
- JiraUpgradeCta,
JiraIssueCreationVulnerabilities: () =>
import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'),
},
@@ -45,11 +43,6 @@ export default {
required: false,
default: null,
},
- upgradePlanPath: {
- type: String,
- required: false,
- default: '',
- },
isValidated: {
type: Boolean,
required: false,
@@ -64,6 +57,9 @@ export default {
},
computed: {
...mapGetters(['isInheriting']),
+ checkboxDisabled() {
+ return !this.showJiraIssuesIntegration || this.isInheriting;
+ },
validProjectKey() {
return !this.enableJiraIssues || Boolean(this.projectKey) || !this.isValidated;
},
@@ -85,64 +81,48 @@ export default {
<template>
<div>
- <template v-if="showJiraIssuesIntegration">
- <input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
- <gl-form-checkbox
- v-model="enableJiraIssues"
- :disabled="isInheriting"
- data-qa-selector="service_jira_issues_enabled_checkbox"
- >
- {{ $options.i18n.enableCheckboxLabel }}
- <template #help>
- {{ $options.i18n.enableCheckboxHelp }}
- </template>
- </gl-form-checkbox>
+ <input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
+ <gl-form-checkbox
+ v-model="enableJiraIssues"
+ :disabled="checkboxDisabled"
+ data-qa-selector="service_jira_issues_enabled_checkbox"
+ >
+ {{ $options.i18n.enableCheckboxLabel }}
+ <template #help>
+ {{ $options.i18n.enableCheckboxHelp }}
+ </template>
+ </gl-form-checkbox>
- <div v-if="enableJiraIssues" class="gl-pl-6 gl-mt-3">
- <gl-form-group
- :label="$options.i18n.projectKeyLabel"
- label-for="service_project_key"
- :invalid-feedback="$options.i18n.requiredFieldFeedback"
+ <div v-if="enableJiraIssues" class="gl-pl-6 gl-mt-3">
+ <gl-form-group
+ :label="$options.i18n.projectKeyLabel"
+ label-for="service_project_key"
+ :invalid-feedback="$options.i18n.requiredFieldFeedback"
+ :state="validProjectKey"
+ class="gl-max-w-26"
+ data-testid="project-key-form-group"
+ >
+ <gl-form-input
+ id="service_project_key"
+ v-model="projectKey"
+ name="service[project_key]"
+ data-qa-selector="service_jira_project_key_field"
+ :placeholder="$options.i18n.projectKeyPlaceholder"
+ :required="enableJiraIssues"
:state="validProjectKey"
- class="gl-max-w-26"
- data-testid="project-key-form-group"
- >
- <gl-form-input
- id="service_project_key"
- v-model="projectKey"
- name="service[project_key]"
- data-qa-selector="service_jira_project_key_field"
- :placeholder="$options.i18n.projectKeyPlaceholder"
- :required="enableJiraIssues"
- :state="validProjectKey"
- :readonly="isInheriting"
- />
- </gl-form-group>
-
- <jira-issue-creation-vulnerabilities
- :project-key="projectKey"
- :initial-is-enabled="initialEnableJiraVulnerabilities"
- :initial-issue-type-id="initialVulnerabilitiesIssuetype"
- :show-full-feature="showJiraVulnerabilitiesIntegration"
- class="gl-mt-6"
- data-testid="jira-for-vulnerabilities"
- @request-jira-issue-types="$emit('request-jira-issue-types')"
- />
- <jira-upgrade-cta
- v-if="!showJiraVulnerabilitiesIntegration"
- class="gl-mt-2 gl-ml-6"
- data-testid="ultimate-upgrade-cta"
- show-ultimate-message
- :upgrade-plan-path="upgradePlanPath"
+ :readonly="isInheriting"
/>
- </div>
- </template>
+ </gl-form-group>
- <jira-upgrade-cta
- v-else
- data-testid="premium-upgrade-cta"
- show-premium-message
- :upgrade-plan-path="upgradePlanPath"
- />
+ <jira-issue-creation-vulnerabilities
+ :project-key="projectKey"
+ :initial-is-enabled="initialEnableJiraVulnerabilities"
+ :initial-issue-type-id="initialVulnerabilitiesIssuetype"
+ :show-full-feature="showJiraVulnerabilitiesIntegration"
+ class="gl-mt-6"
+ data-testid="jira-for-vulnerabilities"
+ @request-jira-issue-types="$emit('request-jira-issue-types')"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js
index 9a9aae36657..92e6ca509c3 100644
--- a/app/assets/javascripts/integrations/edit/index.js
+++ b/app/assets/javascripts/integrations/edit/index.js
@@ -23,6 +23,7 @@ function parseDatasetToProps(data) {
projectKey,
upgradePlanPath,
learnMorePath,
+ aboutPricingUrl,
triggerEvents,
sections,
fields,
@@ -82,6 +83,7 @@ function parseDatasetToProps(data) {
upgradePlanPath,
},
learnMorePath,
+ aboutPricingUrl,
triggerEvents: JSON.parse(triggerEvents),
sections: JSON.parse(sections, { deep: true }),
fields: convertObjectPropsToCamelCase(JSON.parse(fields), { deep: true }),
diff --git a/app/assets/javascripts/invite_members/components/import_a_project_modal.vue b/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
index d71468284ca..fb6c376cfe6 100644
--- a/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
+++ b/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
@@ -145,7 +145,7 @@ export default {
<gl-button
:disabled="importDisabled"
:loading="isLoading"
- variant="success"
+ variant="confirm"
data-testid="import-button"
@click="submitImport"
>{{ $options.i18n.modalPrimaryButton }}</gl-button
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index a9aa0e9b760..7857b9d86d2 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -88,6 +88,11 @@ export default {
type: Array,
required: true,
},
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
return {
@@ -146,6 +151,18 @@ export default {
isOnLearnGitlab() {
return this.source === LEARN_GITLAB;
},
+ reachedLimit() {
+ if (this.usersLimitDataset.freeUsersLimit && this.usersLimitDataset.membersCount) {
+ return this.usersLimitDataset.membersCount >= this.usersLimitDataset.freeUsersLimit;
+ }
+
+ return false;
+ },
+ formGroupDescription() {
+ return this.reachedLimit
+ ? this.$options.labels.placeHolderDisabled
+ : this.$options.labels.placeHolder;
+ },
},
mounted() {
eventHub.$on('openModal', (options) => {
@@ -274,12 +291,14 @@ export default {
:help-link="helpLink"
:label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField"
- :form-group-description="$options.labels.placeHolder"
+ :form-group-description="formGroupDescription"
:submit-disabled="inviteDisabled"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
:new-users-to-invite="newUsersToInvite"
:root-group-id="rootId"
+ :reached-limit="reachedLimit"
+ :users-limit-dataset="usersLimitDataset"
@reset="resetFields"
@submit="sendInvite"
@access-level="onAccessLevelUpdate"
@@ -294,7 +313,10 @@ export default {
</template>
<template #user-limit-notification>
- <user-limit-notification />
+ <user-limit-notification
+ :reached-limit="reachedLimit"
+ :users-limit-dataset="usersLimitDataset"
+ />
</template>
<template #select="{ validationState, labelId }">
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index d9297614a7e..33d37b809c2 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -8,7 +8,9 @@ import {
GlLink,
GlSprintf,
GlFormInput,
+ GlIcon,
} from '@gitlab/ui';
+import Tracking from '~/tracking';
import { sprintf } from '~/locale';
import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
@@ -16,8 +18,13 @@ import {
ACCESS_EXPIRE_DATE,
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
+ INVITE_BUTTON_TEXT_DISABLED,
CANCEL_BUTTON_TEXT,
+ CANCEL_BUTTON_TEXT_DISABLED,
HEADER_CLOSE_LABEL,
+ ON_SHOW_TRACK_LABEL,
+ ON_CLOSE_TRACK_LABEL,
+ ON_SUBMIT_TRACK_LABEL,
} from '../constants';
const DEFAULT_SLOT = 'default';
@@ -41,8 +48,10 @@ export default {
GlDropdownItem,
GlSprintf,
GlFormInput,
+ GlIcon,
ContentTransition,
},
+ mixins: [Tracking.mixin()],
inheritAttrs: false,
props: {
modalTitle: {
@@ -122,6 +131,16 @@ export default {
required: false,
default: false,
},
+ reachedLimit: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
// Be sure to check out reset!
@@ -151,20 +170,27 @@ export default {
},
actionPrimary() {
return {
- text: this.submitButtonText,
+ text: this.reachedLimit ? INVITE_BUTTON_TEXT_DISABLED : this.submitButtonText,
attributes: {
variant: 'confirm',
- disabled: this.submitDisabled,
- loading: this.isLoading,
+ disabled: this.reachedLimit ? false : this.submitDisabled,
+ loading: this.reachedLimit ? false : this.isLoading,
'data-qa-selector': 'invite_button',
+ ...(this.reachedLimit && { href: this.usersLimitDataset.membersPath }),
},
};
},
actionCancel() {
+ if (this.reachedLimit && this.usersLimitDataset.userNamespace) return undefined;
+
return {
- text: this.cancelButtonText,
+ text: this.reachedLimit ? CANCEL_BUTTON_TEXT_DISABLED : this.cancelButtonText,
+ ...(this.reachedLimit && { attributes: { href: this.usersLimitDataset.purchasePath } }),
};
},
+ selectLabelClass() {
+ return `col-form-label ${this.reachedLimit ? 'gl-text-gray-500' : ''}`;
+ },
},
watch: {
selectedAccessLevel: {
@@ -183,15 +209,24 @@ export default {
this.$emit('reset');
},
+ onShowModal() {
+ if (this.reachedLimit) {
+ this.track('render', { category: 'default', label: ON_SHOW_TRACK_LABEL });
+ }
+ },
onCloseModal(e) {
- if (this.preventCancelDefault) {
+ if (this.preventCancelDefault || this.reachedLimit) {
e.preventDefault();
} else {
this.onReset();
this.$refs.modal.hide();
}
- this.$emit('cancel');
+ if (this.reachedLimit) {
+ this.track('click_button', { category: 'default', label: ON_CLOSE_TRACK_LABEL });
+ } else {
+ this.$emit('cancel');
+ }
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
@@ -200,10 +235,14 @@ export default {
// We never want to hide when submitting
e.preventDefault();
- this.$emit('submit', {
- accessLevel: this.selectedAccessLevel,
- expiresAt: this.selectedDate,
- });
+ if (this.reachedLimit) {
+ this.track('click_button', { category: 'default', label: ON_SUBMIT_TRACK_LABEL });
+ } else {
+ this.$emit('submit', {
+ accessLevel: this.selectedAccessLevel,
+ expiresAt: this.selectedDate,
+ });
+ }
},
},
HEADER_CLOSE_LABEL,
@@ -227,6 +266,7 @@ export default {
:header-close-label="$options.HEADER_CLOSE_LABEL"
:action-primary="actionPrimary"
:action-cancel="actionCancel"
+ @shown="onShowModal"
@primary="onSubmit"
@cancel="onCloseModal"
@hidden="onReset"
@@ -255,64 +295,73 @@ export default {
<gl-form-group
:invalid-feedback="invalidFeedbackMessage"
:state="validationState"
- :description="formGroupDescription"
data-testid="members-form-group"
>
- <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
- <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
+ <template #description>
+ <gl-icon v-if="reachedLimit" name="lock" />
+ {{ formGroupDescription }}
+ </template>
+
+ <label :id="selectLabelId" :class="selectLabelClass">{{ labelSearchField }}</label>
+ <gl-form-input v-if="reachedLimit" data-testid="disabled-input" disabled />
+ <slot v-else name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
</gl-form-group>
- <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown
- class="gl-shadow-none gl-w-full"
- data-qa-selector="access_level_dropdown"
- v-bind="$attrs"
- :text="selectedRoleName"
- >
- <template v-for="(key, item) in accessLevels">
- <gl-dropdown-item
- :key="key"
- active-class="is-active"
- is-check-item
- :is-checked="key === selectedAccessLevel"
- @click="changeSelectedItem(key)"
- >
- <div>{{ item }}</div>
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
- </div>
+ <template v-if="!reachedLimit">
+ <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-sprintf :message="$options.READ_MORE_TEXT">
- <template #link="{ content }">
- <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-dropdown
+ class="gl-shadow-none gl-w-full"
+ data-qa-selector="access_level_dropdown"
+ v-bind="$attrs"
+ :text="selectedRoleName"
+ >
+ <template v-for="(key, item) in accessLevels">
+ <gl-dropdown-item
+ :key="key"
+ active-class="is-active"
+ is-check-item
+ :is-checked="key === selectedAccessLevel"
+ @click="changeSelectedItem(key)"
+ >
+ <div>{{ item }}</div>
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
+ </div>
- <label class="gl-mt-5 gl-display-block" for="expires_at">{{
- $options.ACCESS_EXPIRE_DATE
- }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
- <gl-datepicker
- v-model="selectedDate"
- class="gl-display-inline!"
- :min-date="minDate"
- :target="null"
- >
- <template #default="{ formattedDate }">
- <gl-form-input
- class="gl-w-full"
- :value="formattedDate"
- :placeholder="__(`YYYY-MM-DD`)"
- />
- </template>
- </gl-datepicker>
- </div>
- <slot name="form-after"></slot>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-sprintf :message="$options.READ_MORE_TEXT">
+ <template #link="{ content }">
+ <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+
+ <label class="gl-mt-5 gl-display-block" for="expires_at">{{
+ $options.ACCESS_EXPIRE_DATE
+ }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
+ <gl-datepicker
+ v-model="selectedDate"
+ class="gl-display-inline!"
+ :min-date="minDate"
+ :target="null"
+ >
+ <template #default="{ formattedDate }">
+ <gl-form-input
+ class="gl-w-full"
+ :value="formattedDate"
+ :placeholder="__(`YYYY-MM-DD`)"
+ />
+ </template>
+ </gl-datepicker>
+ </div>
+ <slot name="form-after"></slot>
+ </template>
</template>
+
<template v-for="{ key } in extraSlots" #[key]>
<slot :name="key"></slot>
</template>
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index 0a191f6d406..30c9294344e 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -134,10 +134,10 @@ export default {
:hide-dropdown-with-no-items="hideDropdownWithNoItems"
:placeholder="placeholderText"
:aria-labelledby="ariaLabelledby"
- :text-input-attrs="{
+ :text-input-attrs="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
'data-testid': 'members-token-select-input',
'data-qa-selector': 'members_token_select_input',
- }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@blur="handleBlur"
@text-input="handleTextInput"
@input="handleInput"
diff --git a/app/assets/javascripts/invite_members/components/user_limit_notification.vue b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
index beef1aef8a1..ea5f4317d86 100644
--- a/app/assets/javascripts/invite_members/components/user_limit_notification.vue
+++ b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
@@ -1,35 +1,51 @@
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { s__, n__, sprintf } from '~/locale';
+import { n__, sprintf } from '~/locale';
-const CLOSE_TO_LIMIT_COUNT = 2;
-
-const WARNING_ALERT_TITLE = s__(
- 'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
-);
-
-const DANGER_ALERT_TITLE = s__(
- "InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
-);
-
-const CLOSE_TO_LIMIT_MESSAGE = s__(
- 'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
-);
+import {
+ WARNING_ALERT_TITLE,
+ DANGER_ALERT_TITLE,
+ REACHED_LIMIT_MESSAGE,
+ REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE,
+ CLOSE_TO_LIMIT_MESSAGE,
+} from '../constants';
-const REACHED_LIMIT_MESSAGE = s__(
- 'InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need.',
-).concat(' ', CLOSE_TO_LIMIT_MESSAGE);
+const CLOSE_TO_LIMIT_COUNT = 2;
export default {
name: 'UserLimitNotification',
components: { GlAlert, GlSprintf, GlLink },
- inject: ['name', 'newTrialRegistrationPath', 'purchasePath', 'freeUsersLimit', 'membersCount'],
+ inject: ['name'],
+ props: {
+ reachedLimit: {
+ type: Boolean,
+ required: true,
+ },
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
computed: {
- reachedLimit() {
- return this.isLimit();
+ freeUsersLimit() {
+ return this.usersLimitDataset.freeUsersLimit;
+ },
+ membersCount() {
+ return this.usersLimitDataset.membersCount;
+ },
+ newTrialRegistrationPath() {
+ return this.usersLimitDataset.newTrialRegistrationPath;
+ },
+ purchasePath() {
+ return this.usersLimitDataset.purchasePath;
},
closeToLimit() {
- return this.isLimit(CLOSE_TO_LIMIT_COUNT);
+ if (this.freeUsersLimit && this.membersCount) {
+ return this.membersCount >= this.freeUsersLimit - CLOSE_TO_LIMIT_COUNT;
+ }
+
+ return false;
},
warningAlertTitle() {
return sprintf(WARNING_ALERT_TITLE, {
@@ -51,28 +67,29 @@ export default {
title() {
return this.reachedLimit ? this.dangerAlertTitle : this.warningAlertTitle;
},
+ reachedLimitMessage() {
+ if (this.usersLimitDataset.userNamespace) {
+ return this.$options.i18n.reachedLimitMessage;
+ }
+
+ return this.$options.i18n.reachedLimitUpgradeSuggestionMessage;
+ },
message() {
if (this.reachedLimit) {
- return this.$options.i18n.reachedLimitMessage;
+ return this.reachedLimitMessage;
}
return this.$options.i18n.closeToLimitMessage;
},
},
methods: {
- isLimit(deviation = 0) {
- if (this.freeUsersLimit && this.membersCount) {
- return this.membersCount >= this.freeUsersLimit - deviation;
- }
-
- return false;
- },
pluralMembers(count) {
return n__('member', 'members', count);
},
},
i18n: {
reachedLimitMessage: REACHED_LIMIT_MESSAGE,
+ reachedLimitUpgradeSuggestionMessage: REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE,
closeToLimitMessage: CLOSE_TO_LIMIT_MESSAGE,
},
};
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 3cd0bfc0181..928f79f1c8d 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -35,8 +35,11 @@ export const MEMBERS_TO_PROJECT_DEFAULT_INTRO_TEXT = s__(
export const MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT = s__(
"InviteMembersModal|Congratulations on creating your project, you're almost there!",
);
-export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|GitLab member or email address');
+export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|Username or email address');
export const MEMBERS_PLACEHOLDER = s__('InviteMembersModal|Select members or type email addresses');
+export const MEMBERS_PLACEHOLDER_DISABLED = s__(
+ 'InviteMembersModal|This feature is disabled until this group has space for more members.',
+);
export const MEMBERS_TASKS_TO_BE_DONE_TITLE = s__(
'InviteMembersModal|Create issues for your new team member to work on (optional)',
);
@@ -66,7 +69,9 @@ export const READ_MORE_TEXT = s__(
`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`,
);
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
+export const INVITE_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Manage members');
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
+export const CANCEL_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Explore paid plans');
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
export const MEMBER_MODAL_LABELS = {
@@ -94,6 +99,7 @@ export const MEMBER_MODAL_LABELS = {
},
searchField: MEMBERS_SEARCH_FIELD,
placeHolder: MEMBERS_PLACEHOLDER,
+ placeHolderDisabled: MEMBERS_PLACEHOLDER_DISABLED,
tasksToBeDone: {
title: MEMBERS_TASKS_TO_BE_DONE_TITLE,
noProjects: MEMBERS_TASKS_TO_BE_DONE_NO_PROJECTS,
@@ -118,3 +124,27 @@ export const GROUP_MODAL_LABELS = {
};
export const LEARN_GITLAB = 'learn_gitlab';
+export const ON_SHOW_TRACK_LABEL = 'locked_modal_viewed';
+export const ON_CLOSE_TRACK_LABEL = 'explore_paid_plans_clicked';
+export const ON_SUBMIT_TRACK_LABEL = 'manage_members_clicked';
+
+export const WARNING_ALERT_TITLE = s__(
+ 'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
+);
+export const DANGER_ALERT_TITLE = s__(
+ "InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
+);
+
+export const REACHED_LIMIT_MESSAGE = s__(
+ 'InviteMembersModal|You cannot add more members, but you can remove members who no longer need access.',
+);
+
+export const REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE = REACHED_LIMIT_MESSAGE.concat(
+ s__(
+ 'InviteMembersModal| To get more members and access to additional paid features, an owner of this namespace can start a trial or upgrade to a paid tier.',
+ ),
+);
+
+export const CLOSE_TO_LIMIT_MESSAGE = s__(
+ 'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
+);
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index 958121ad735..a4be3f205a3 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -1,7 +1,7 @@
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
Vue.use(GlToast);
@@ -26,10 +26,6 @@ export default (function initInviteMembersModal() {
provide: {
name: el.dataset.name,
newProjectPath: el.dataset.newProjectPath,
- newTrialRegistrationPath: el.dataset.newTrialRegistrationPath,
- purchasePath: el.dataset.purchasePath,
- freeUsersLimit: el.dataset.freeUsersLimit && parseInt(el.dataset.freeUsersLimit, 10),
- membersCount: el.dataset.membersCount && parseInt(el.dataset.membersCount, 10),
},
render: (createElement) =>
createElement(InviteMembersModal, {
@@ -42,6 +38,9 @@ export default (function initInviteMembersModal() {
projects: JSON.parse(el.dataset.projects || '[]'),
usersFilter: el.dataset.usersFilter,
filterId: parseInt(el.dataset.filterId, 10),
+ usersLimitDataset: convertObjectPropsToCamelCase(
+ JSON.parse(el.dataset.usersLimitDataset || '{}'),
+ ),
},
}),
});
diff --git a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
index 82223ab9ef4..06d1a2ee233 100644
--- a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
+++ b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
@@ -2,14 +2,21 @@
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { __ } from '~/locale';
+import { IssuableType, WorkspaceType } from '~/issues/constants';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
export default {
+ WorkspaceType,
+ IssuableType,
components: {
GlIcon,
+ ConfidentialityBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagMixin()],
inject: ['hidden'],
computed: {
...mapGetters(['getNoteableData']),
@@ -19,6 +26,9 @@ export default {
isConfidential() {
return this.getNoteableData.confidential;
},
+ isMergeRequest() {
+ return this.getNoteableData.targetType === 'merge_request' && this.glFeatures.updatedMrHeader;
+ },
warningIconsMeta() {
return [
{
@@ -27,11 +37,6 @@ export default {
dataTestId: 'locked',
},
{
- iconName: 'eye-slash',
- visible: this.isConfidential,
- dataTestId: 'confidential',
- },
- {
iconName: 'spam',
visible: this.hidden,
dataTestId: 'hidden',
@@ -45,6 +50,12 @@ export default {
<template>
<div class="gl-display-inline-block">
+ <confidentiality-badge
+ v-if="isConfidential"
+ data-testid="confidential"
+ :workspace-type="$options.WorkspaceType.project"
+ :issuable-type="$options.IssuableType.Issue"
+ />
<template v-for="meta in warningIconsMeta">
<div
v-if="meta.visible"
@@ -52,7 +63,11 @@ export default {
v-gl-tooltip
:data-testid="meta.dataTestId"
:title="meta.tooltip || null"
- class="issuable-warning-icon inline"
+ :class="{
+ 'gl-mr-3 gl-mt-2 gl-display-flex gl-justify-content-center gl-align-items-center': isMergeRequest,
+ 'gl-display-inline-block': !isMergeRequest,
+ }"
+ class="issuable-warning-icon"
>
<gl-icon :name="meta.iconName" class="icon" />
</div>
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue
index 2bb0e3c80f9..dfe18567608 100644
--- a/app/assets/javascripts/issuable/components/related_issuable_item.vue
+++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue
@@ -116,7 +116,7 @@ export default {
<div
class="item-path-area item-path-id d-flex align-items-center mr-2 mt-2 mt-xl-0 ml-xl-2"
>
- <gl-tooltip :target="() => this.$refs.iconElement">
+ <gl-tooltip :target="() => $refs.iconElement">
<span v-safe-html="stateTitle"></span>
</gl-tooltip>
<span v-gl-tooltip :title="itemPath" class="path-id-text d-inline-block">{{
diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue
index bd6fdc131cb..498dc859186 100644
--- a/app/assets/javascripts/issuable/components/status_box.vue
+++ b/app/assets/javascripts/issuable/components/status_box.vue
@@ -1,32 +1,50 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlBadge, GlIcon } from '@gitlab/ui';
import Vue from 'vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale';
+import { IssuableType } from '~/issues/constants';
+import { IssuableStates } from '~/vue_shared/issuable/list/constants';
-export const statusBoxState = Vue.observable({
+export const badgeState = Vue.observable({
state: '',
updateStatus: null,
});
const CLASSES = {
- opened: 'status-box-open',
- locked: 'status-box-open',
- closed: 'status-box-mr-closed',
- merged: 'status-box-mr-merged',
+ opened: 'issuable-status-badge-open',
+ locked: 'issuable-status-badge-open',
+ closed: 'issuable-status-badge-closed',
+ merged: 'issuable-status-badge-merged',
+};
+
+const ISSUE_ICONS = {
+ opened: 'issues',
+ locked: 'issues',
+ closed: 'issue-closed',
+};
+
+const MERGE_REQUEST_ICONS = {
+ opened: 'merge-request-open',
+ locked: 'merge-request-open',
+ closed: 'merge-request-close',
+ merged: 'merge',
};
const STATUS = {
- opened: [__('Open'), 'issue-open-m'],
- locked: [__('Open'), 'issue-open-m'],
- closed: [__('Closed'), 'issue-close'],
- merged: [__('Merged'), 'git-merge'],
+ opened: __('Open'),
+ locked: __('Open'),
+ closed: __('Closed'),
+ merged: __('Merged'),
};
export default {
components: {
+ GlBadge,
GlIcon,
},
+ mixins: [glFeatureFlagMixin()],
inject: {
query: { default: null },
projectPath: { default: null },
@@ -46,30 +64,47 @@ export default {
},
data() {
if (this.initialState) {
- statusBoxState.state = this.initialState;
+ badgeState.state = this.initialState;
}
- return statusBoxState;
+ return badgeState;
},
computed: {
- statusBoxClass() {
- return CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state];
+ badgeClass() {
+ return [
+ CLASSES[this.state],
+ {
+ 'gl-vertical-align-bottom':
+ this.issuableType === IssuableType.MergeRequest && this.glFeatures.updatedMrHeader,
+ },
+ ];
+ },
+ badgeVariant() {
+ if (this.state === IssuableStates.Opened) {
+ return 'success';
+ } else if (this.state === IssuableStates.Closed) {
+ return this.issuableType === IssuableType.MergeRequest ? 'danger' : 'info';
+ }
+ return 'info';
},
- statusHumanName() {
- return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[0];
+ badgeText() {
+ return STATUS[this.state];
},
- statusIconName() {
- return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[1];
+ badgeIcon() {
+ if (this.issuableType === IssuableType.Issue) {
+ return ISSUE_ICONS[this.state];
+ }
+ return MERGE_REQUEST_ICONS[this.state];
},
},
created() {
- if (!statusBoxState.updateStatus) {
- statusBoxState.updateStatus = this.fetchState;
+ if (!badgeState.updateStatus) {
+ badgeState.updateStatus = this.fetchState;
}
},
beforeDestroy() {
- if (statusBoxState.updateStatus && this.query) {
- statusBoxState.updateStatus = null;
+ if (badgeState.updateStatus && this.query) {
+ badgeState.updateStatus = null;
}
},
methods: {
@@ -83,17 +118,15 @@ export default {
fetchPolicy: fetchPolicies.NO_CACHE,
});
- statusBoxState.state = data?.workspace?.issuable?.state;
+ badgeState.state = data?.workspace?.issuable?.state;
},
},
};
</script>
<template>
- <div :class="statusBoxClass" class="issuable-status-box status-box">
- <gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" />
- <span class="gl-display-none gl-sm-display-block">
- {{ statusHumanName }}
- </span>
- </div>
+ <gl-badge class="issuable-status-badge gl-mr-3" :class="badgeClass" :variant="badgeVariant">
+ <gl-icon :name="badgeIcon" />
+ <span class="gl-display-none gl-sm-display-block gl-ml-2">{{ badgeText }}</span>
+ </gl-badge>
</template>
diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js
index 018cadad50f..8e76a33c7dd 100644
--- a/app/assets/javascripts/issuable/issuable_form.js
+++ b/app/assets/javascripts/issuable/issuable_form.js
@@ -65,7 +65,8 @@ export default class IssuableForm {
this.gfmAutoComplete = new GfmAutoComplete(
gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
).setup();
- this.usersSelect = new UsersSelect();
+ const autoAssignToMe = form.get(0).id === 'new_merge_request';
+ this.usersSelect = new UsersSelect(undefined, undefined, { autoAssignToMe });
this.reviewersSelect = new UsersSelect(undefined, '.js-reviewer-search');
this.zenMode = new ZenMode();
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js
index c96af6da720..8294c018117 100644
--- a/app/assets/javascripts/issues/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js
@@ -69,12 +69,12 @@ export default class CreateMergeRequestDropdown {
// with user's inputs.
this.regexps = {
branch: {
- createBranchPath: new RegExp('(branch_name=)(.+?)(?=&issue)'),
- createMrPath: new RegExp('(source_branch%5D=)(.+?)(?=&)'),
+ createBranchPath: /(branch_name=)(.+?)(?=&issue)/,
+ createMrPath: /(source_branch%5D=)(.+?)(?=&)/,
},
ref: {
- createBranchPath: new RegExp('(ref=)(.+?)$'),
- createMrPath: new RegExp('(target_branch%5D=)(.+?)$'),
+ createBranchPath: /(ref=)(.+?)$/,
+ createMrPath: /(target_branch%5D=)(.+?)$/,
},
};
@@ -170,7 +170,6 @@ export default class CreateMergeRequestDropdown {
createMergeRequest() {
return new Promise(() => {
this.isCreatingMergeRequest = true;
-
return this.createBranch().then(() => {
let path = canCreateConfidentialMergeRequest()
? this.createMrPath.replace(
diff --git a/app/assets/javascripts/issues/issue.js b/app/assets/javascripts/issues/issue.js
index 8e27f547b5c..a9321cf200d 100644
--- a/app/assets/javascripts/issues/issue.js
+++ b/app/assets/javascripts/issues/issue.js
@@ -49,8 +49,8 @@ export default class Issue {
issueFailMessage = __('Unable to update this issue at this time.'),
) {
if ('id' in data) {
- const isClosedBadge = $('div.status-box-issue-closed');
- const isOpenBadge = $('div.status-box-open');
+ const isClosedBadge = $('.issuable-status-badge-closed');
+ const isOpenBadge = $('.issuable-status-badge-open');
const projectIssuesCounter = $('.issue_counter');
isClosedBadge.toggleClass('hidden', !isClosed);
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index a43aed6c521..b81ab103271 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -13,6 +13,8 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
+import getIssuesWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_without_crm.query.graphql';
+import getIssuesCountsWithoutCrmQuery from 'ee_else_ce/issues/list/queries/get_issues_counts_without_crm.query.graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -45,6 +47,7 @@ import {
PAGE_SIZE,
PARAM_PAGE_AFTER,
PARAM_PAGE_BEFORE,
+ PARAM_SORT,
PARAM_STATE,
RELATIVE_POSITION_ASC,
TOKEN_TYPE_ASSIGNEE,
@@ -122,6 +125,7 @@ export default {
'isAnonymousSearchDisabled',
'isIssueRepositioningDisabled',
'isProject',
+ 'isPublicVisibilityRestricted',
'isSignedIn',
'jiraIntegrationPath',
'newIssuePath',
@@ -138,48 +142,24 @@ export default {
},
},
data() {
- const pageAfter = getParameterByName(PARAM_PAGE_AFTER);
- const pageBefore = getParameterByName(PARAM_PAGE_BEFORE);
- const state = getParameterByName(PARAM_STATE);
- const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
- const dashboardSortKey = getSortKey(this.initialSort);
- const graphQLSortKey =
- isSortKey(this.initialSort?.toUpperCase()) && this.initialSort.toUpperCase();
-
- // The initial sort is an old enum value when it is saved on the dashboard issues page.
- // The initial sort is a GraphQL enum value when it is saved on the Vue issues list page.
- let sortKey = dashboardSortKey || graphQLSortKey || defaultSortKey;
-
- if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
- this.showIssueRepositioningMessage();
- sortKey = defaultSortKey;
- }
-
- const isSearchDisabled =
- this.isAnonymousSearchDisabled &&
- !this.isSignedIn &&
- window.location.search.includes('search=');
-
- if (isSearchDisabled) {
- this.showAnonymousSearchingMessage();
- }
-
return {
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
- filterTokens: isSearchDisabled ? [] : getFilterTokens(window.location.search),
+ filterTokens: [],
issues: [],
issuesCounts: {},
issuesError: null,
pageInfo: {},
- pageParams: getInitialPageParams(sortKey, pageAfter, pageBefore),
+ pageParams: {},
showBulkEditSidebar: false,
- sortKey,
- state: state || IssuableStates.Opened,
+ sortKey: CREATED_DESC,
+ state: IssuableStates.Opened,
};
},
apollo: {
issues: {
- query: getIssuesQuery,
+ query() {
+ return this.hasCrmParameter ? getIssuesQuery : getIssuesWithoutCrmQuery;
+ },
variables() {
return this.queryVariables;
},
@@ -203,7 +183,9 @@ export default {
debounce: 200,
},
issuesCounts: {
- query: getIssuesCountsQuery,
+ query() {
+ return this.hasCrmParameter ? getIssuesCountsQuery : getIssuesCountsWithoutCrmQuery;
+ },
variables() {
return this.queryVariables;
},
@@ -228,6 +210,7 @@ export default {
const isIidSearch = ISSUE_REFERENCE.test(this.searchQuery);
return {
fullPath: this.fullPath,
+ hideUsers: this.isPublicVisibilityRestricted && !this.isSignedIn,
iid: isIidSearch ? this.searchQuery.slice(1) : undefined,
isProject: this.isProject,
isSignedIn: this.isSignedIn,
@@ -415,8 +398,22 @@ export default {
...this.urlFilterParams,
};
},
+ hasCrmParameter() {
+ return (
+ window.location.search.includes('crm_contact_id=') ||
+ window.location.search.includes('crm_organization_id=')
+ );
+ },
+ },
+ watch: {
+ $route(newValue, oldValue) {
+ if (newValue.fullPath !== oldValue.fullPath) {
+ this.updateData(getParameterByName(PARAM_SORT));
+ }
+ },
},
created() {
+ this.updateData(this.initialSort);
this.cache = {};
},
mounted() {
@@ -516,6 +513,8 @@ export default {
this.pageParams = getInitialPageParams(this.sortKey);
}
this.state = state;
+
+ this.$router.push({ query: this.urlParams });
},
handleDismissAlert() {
this.issuesError = null;
@@ -525,8 +524,11 @@ export default {
this.showAnonymousSearchingMessage();
return;
}
+
this.pageParams = getInitialPageParams(this.sortKey);
this.filterTokens = filter;
+
+ this.$router.push({ query: this.urlParams });
},
handleNextPage() {
this.pageParams = {
@@ -534,6 +536,8 @@ export default {
firstPageSize: PAGE_SIZE,
};
scrollUp();
+
+ this.$router.push({ query: this.urlParams });
},
handlePreviousPage() {
this.pageParams = {
@@ -541,6 +545,8 @@ export default {
lastPageSize: PAGE_SIZE,
};
scrollUp();
+
+ this.$router.push({ query: this.urlParams });
},
handleReorder({ newIndex, oldIndex }) {
const issueToMove = this.issues[oldIndex];
@@ -592,6 +598,8 @@ export default {
if (this.isSignedIn) {
this.saveSortPreference(sortKey);
}
+
+ this.$router.push({ query: this.urlParams });
},
saveSortPreference(sortKey) {
this.$apollo
@@ -623,6 +631,39 @@ export default {
toggleBulkEditSidebar(showBulkEditSidebar) {
this.showBulkEditSidebar = showBulkEditSidebar;
},
+ updateData(sortValue) {
+ const pageAfter = getParameterByName(PARAM_PAGE_AFTER);
+ const pageBefore = getParameterByName(PARAM_PAGE_BEFORE);
+ const state = getParameterByName(PARAM_STATE);
+
+ const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
+ const dashboardSortKey = getSortKey(sortValue);
+ const graphQLSortKey = isSortKey(sortValue?.toUpperCase()) && sortValue.toUpperCase();
+
+ // The initial sort is an old enum value when it is saved on the dashboard issues page.
+ // The initial sort is a GraphQL enum value when it is saved on the Vue issues list page.
+ let sortKey = dashboardSortKey || graphQLSortKey || defaultSortKey;
+
+ if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
+ this.showIssueRepositioningMessage();
+ sortKey = defaultSortKey;
+ }
+
+ const isSearchDisabled =
+ this.isAnonymousSearchDisabled &&
+ !this.isSignedIn &&
+ window.location.search.includes('search=');
+
+ if (isSearchDisabled) {
+ this.showAnonymousSearchingMessage();
+ }
+
+ this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
+ this.filterTokens = isSearchDisabled ? [] : getFilterTokens(window.location.search);
+ this.pageParams = getInitialPageParams(sortKey, pageAfter, pageBefore);
+ this.sortKey = sortKey;
+ this.state = state || IssuableStates.Opened;
+ },
},
};
</script>
@@ -649,10 +690,10 @@ export default {
:is-manual-ordering="isManualOrdering"
:show-bulk-edit-sidebar="showBulkEditSidebar"
:show-pagination-controls="showPaginationControls"
- :use-keyset-pagination="true"
+ sync-filter-and-sort
+ use-keyset-pagination
:has-next-page="pageInfo.hasNextPage"
:has-previous-page="pageInfo.hasPreviousPage"
- :url-params="urlParams"
@click-tab="handleClickTab"
@dismiss-alert="handleDismissAlert"
@filter="handleFilter"
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 4b07a078512..0795df10a7c 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -56,8 +56,10 @@ export const ISSUE_REFERENCE = /^#\d+$/;
export const MAX_LIST_SIZE = 10;
export const PAGE_SIZE = 20;
export const PAGE_SIZE_MANUAL = 100;
+export const PARAM_ASSIGNEE_ID = 'assignee_id';
export const PARAM_PAGE_AFTER = 'page_after';
export const PARAM_PAGE_BEFORE = 'page_before';
+export const PARAM_SORT = 'sort';
export const PARAM_STATE = 'state';
export const RELATIVE_POSITION = 'relative_position';
@@ -112,7 +114,8 @@ export const URL_PARAM = 'urlParam';
export const NORMAL_FILTER = 'normalFilter';
export const SPECIAL_FILTER = 'specialFilter';
export const ALTERNATIVE_FILTER = 'alternativeFilter';
-export const SPECIAL_FILTER_VALUES = [
+
+export const specialFilterValues = [
FILTER_NONE,
FILTER_ANY,
FILTER_CURRENT,
@@ -131,6 +134,8 @@ export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
export const TOKEN_TYPE_ITERATION = 'iteration';
export const TOKEN_TYPE_EPIC = 'epic_id';
export const TOKEN_TYPE_WEIGHT = 'weight';
+export const TOKEN_TYPE_CONTACT = 'crm_contact';
+export const TOKEN_TYPE_ORGANIZATION = 'crm_organization';
export const filters = {
[TOKEN_TYPE_AUTHOR]: {
@@ -174,6 +179,7 @@ export const filters = {
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[milestone_title]',
+ [SPECIAL_FILTER]: 'not[milestone_title]',
},
},
},
@@ -258,6 +264,7 @@ export const filters = {
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[iteration_id]',
+ [SPECIAL_FILTER]: 'not[iteration_id]',
},
},
},
@@ -291,4 +298,24 @@ export const filters = {
},
},
},
+ [TOKEN_TYPE_CONTACT]: {
+ [API_PARAM]: {
+ [NORMAL_FILTER]: 'crmContactId',
+ },
+ [URL_PARAM]: {
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'crm_contact_id',
+ },
+ },
+ },
+ [TOKEN_TYPE_ORGANIZATION]: {
+ [API_PARAM]: {
+ [NORMAL_FILTER]: 'crmOrganizationId',
+ },
+ [URL_PARAM]: {
+ [OPERATOR_IS]: {
+ [NORMAL_FILTER]: 'crm_organization_id',
+ },
+ },
+ },
};
diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js
index 3b2d37eab74..f5cb160e344 100644
--- a/app/assets/javascripts/issues/list/index.js
+++ b/app/assets/javascripts/issues/list/index.js
@@ -1,6 +1,7 @@
import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import VueRouter from 'vue-router';
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql';
@@ -53,6 +54,7 @@ export function mountIssuesListApp() {
}
Vue.use(VueApollo);
+ Vue.use(VueRouter);
const resolvers = {
Mutation: {
@@ -74,11 +76,6 @@ export function mountIssuesListApp() {
},
};
- const defaultClient = createDefaultClient(resolvers);
- const apolloProvider = new VueApollo({
- defaultClient,
- });
-
const {
autocompleteAwardEmojisPath,
calendarPath,
@@ -104,6 +101,7 @@ export function mountIssuesListApp() {
isAnonymousSearchDisabled,
isIssueRepositioningDisabled,
isProject,
+ isPublicVisibilityRestricted,
isSignedIn,
jiraIntegrationPath,
markdownHelpPath,
@@ -121,7 +119,14 @@ export function mountIssuesListApp() {
return new Vue({
el,
name: 'IssuesListRoot',
- apolloProvider,
+ apolloProvider: new VueApollo({
+ defaultClient: createDefaultClient(resolvers),
+ }),
+ router: new VueRouter({
+ base: window.location.pathname,
+ mode: 'history',
+ routes: [{ path: '/' }],
+ }),
provide: {
autocompleteAwardEmojisPath,
calendarPath,
@@ -140,6 +145,7 @@ export function mountIssuesListApp() {
isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled),
isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled),
isProject: parseBoolean(isProject),
+ isPublicVisibilityRestricted: parseBoolean(isPublicVisibilityRestricted),
isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath,
newIssuePath,
diff --git a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
index ec24ea7c56a..df7016aeb74 100644
--- a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
@@ -2,6 +2,7 @@
#import "./issue.fragment.graphql"
query getIssues(
+ $hideUsers: Boolean = false
$isProject: Boolean = false
$isSignedIn: Boolean = false
$fullPath: ID!
@@ -20,6 +21,8 @@ query getIssues(
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
+ $crmContactId: String
+ $crmOrganizationId: String
$not: NegatedIssueFilterInput
$beforeCursor: String
$afterCursor: String
@@ -43,6 +46,8 @@ query getIssues(
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
before: $beforeCursor
after: $afterCursor
@@ -76,6 +81,8 @@ query getIssues(
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
before: $beforeCursor
after: $afterCursor
diff --git a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql
index 58e7ce32e7c..c1aee772167 100644
--- a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql
@@ -14,6 +14,8 @@ query getIssuesCount(
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
+ $crmContactId: String
+ $crmOrganizationId: String
$not: NegatedIssueFilterInput
) {
group(fullPath: $fullPath) @skip(if: $isProject) {
@@ -32,6 +34,8 @@ query getIssuesCount(
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
@@ -50,6 +54,8 @@ query getIssuesCount(
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
@@ -68,6 +74,8 @@ query getIssuesCount(
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
@@ -90,6 +98,8 @@ query getIssuesCount(
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
@@ -109,6 +119,8 @@ query getIssuesCount(
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
@@ -128,6 +140,8 @@ query getIssuesCount(
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
+ crmContactId: $crmContactId
+ crmOrganizationId: $crmOrganizationId
not: $not
) {
count
diff --git a/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql
new file mode 100644
index 00000000000..ab91aab1218
--- /dev/null
+++ b/app/assets/javascripts/issues/list/queries/get_issues_counts_without_crm.query.graphql
@@ -0,0 +1,136 @@
+query getIssuesCountWithoutCrm(
+ $isProject: Boolean = false
+ $fullPath: ID!
+ $iid: String
+ $search: String
+ $assigneeId: String
+ $assigneeUsernames: [String!]
+ $authorUsername: String
+ $confidential: Boolean
+ $labelName: [String]
+ $milestoneTitle: [String]
+ $milestoneWildcardId: MilestoneWildcardId
+ $myReactionEmoji: String
+ $releaseTag: [String!]
+ $releaseTagWildcardId: ReleaseTagWildcardId
+ $types: [IssueType!]
+ $not: NegatedIssueFilterInput
+) {
+ group(fullPath: $fullPath) @skip(if: $isProject) {
+ id
+ openedIssues: issues(
+ includeSubgroups: true
+ state: opened
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ closedIssues: issues(
+ includeSubgroups: true
+ state: closed
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ allIssues: issues(
+ includeSubgroups: true
+ state: all
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ }
+ project(fullPath: $fullPath) @include(if: $isProject) {
+ id
+ openedIssues: issues(
+ state: opened
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ releaseTag: $releaseTag
+ releaseTagWildcardId: $releaseTagWildcardId
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ closedIssues: issues(
+ state: closed
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ releaseTag: $releaseTag
+ releaseTagWildcardId: $releaseTagWildcardId
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ allIssues: issues(
+ state: all
+ iid: $iid
+ search: $search
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ releaseTag: $releaseTag
+ releaseTagWildcardId: $releaseTagWildcardId
+ types: $types
+ not: $not
+ ) {
+ count
+ }
+ }
+}
diff --git a/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql
new file mode 100644
index 00000000000..4a8b1dfd618
--- /dev/null
+++ b/app/assets/javascripts/issues/list/queries/get_issues_without_crm.query.graphql
@@ -0,0 +1,94 @@
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+#import "./issue.fragment.graphql"
+
+query getIssuesWithoutCrm(
+ $hideUsers: Boolean = false
+ $isProject: Boolean = false
+ $isSignedIn: Boolean = false
+ $fullPath: ID!
+ $iid: String
+ $search: String
+ $sort: IssueSort
+ $state: IssuableState
+ $assigneeId: String
+ $assigneeUsernames: [String!]
+ $authorUsername: String
+ $confidential: Boolean
+ $labelName: [String]
+ $milestoneTitle: [String]
+ $milestoneWildcardId: MilestoneWildcardId
+ $myReactionEmoji: String
+ $releaseTag: [String!]
+ $releaseTagWildcardId: ReleaseTagWildcardId
+ $types: [IssueType!]
+ $not: NegatedIssueFilterInput
+ $beforeCursor: String
+ $afterCursor: String
+ $firstPageSize: Int
+ $lastPageSize: Int
+) {
+ group(fullPath: $fullPath) @skip(if: $isProject) {
+ id
+ issues(
+ includeSubgroups: true
+ iid: $iid
+ search: $search
+ sort: $sort
+ state: $state
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ types: $types
+ not: $not
+ before: $beforeCursor
+ after: $afterCursor
+ first: $firstPageSize
+ last: $lastPageSize
+ ) {
+ pageInfo {
+ ...PageInfo
+ }
+ nodes {
+ ...IssueFragment
+ reference(full: true)
+ }
+ }
+ }
+ project(fullPath: $fullPath) @include(if: $isProject) {
+ id
+ issues(
+ iid: $iid
+ search: $search
+ sort: $sort
+ state: $state
+ assigneeId: $assigneeId
+ assigneeUsernames: $assigneeUsernames
+ authorUsername: $authorUsername
+ confidential: $confidential
+ labelName: $labelName
+ milestoneTitle: $milestoneTitle
+ milestoneWildcardId: $milestoneWildcardId
+ myReactionEmoji: $myReactionEmoji
+ releaseTag: $releaseTag
+ releaseTagWildcardId: $releaseTagWildcardId
+ types: $types
+ not: $not
+ before: $beforeCursor
+ after: $afterCursor
+ first: $firstPageSize
+ last: $lastPageSize
+ ) {
+ pageInfo {
+ ...PageInfo
+ }
+ nodes {
+ ...IssueFragment
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
index d09e4d9df2b..73a13cea94a 100644
--- a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
@@ -17,7 +17,7 @@ fragment IssueFragment on Issue {
userDiscussionsCount @include(if: $isSignedIn)
webPath
webUrl
- assignees {
+ assignees @skip(if: $hideUsers) {
nodes {
__typename
id
@@ -27,7 +27,7 @@ fragment IssueFragment on Issue {
webUrl
}
}
- author {
+ author @skip(if: $hideUsers) {
__typename
id
avatarUrl
diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 4b77bd9bc5f..3ca93069628 100644
--- a/app/assets/javascripts/issues/list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -1,4 +1,6 @@
+import { createTerm } from '@gitlab/ui/src/components/base/filtered_search/filtered_search_utils';
import { isPositiveInteger } from '~/lib/utils/number_utils';
+import { getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import {
FILTERED_SEARCH_TERM,
@@ -20,13 +22,14 @@ import {
NORMAL_FILTER,
PAGE_SIZE,
PAGE_SIZE_MANUAL,
+ PARAM_ASSIGNEE_ID,
POPULARITY_ASC,
POPULARITY_DESC,
PRIORITY_ASC,
PRIORITY_DESC,
RELATIVE_POSITION_ASC,
SPECIAL_FILTER,
- SPECIAL_FILTER_VALUES,
+ specialFilterValues,
TITLE_ASC,
TITLE_DESC,
TOKEN_TYPE_ASSIGNEE,
@@ -195,23 +198,27 @@ const convertToFilteredSearchTerms = (locationSearch) =>
export const getFilterTokens = (locationSearch) => {
if (!locationSearch) {
- return [];
+ return [createTerm()];
}
const filterTokens = convertToFilteredTokens(locationSearch);
const searchTokens = convertToFilteredSearchTerms(locationSearch);
- return filterTokens.concat(searchTokens);
+ const tokens = filterTokens.concat(searchTokens);
+ return tokens.length ? tokens : [createTerm()];
};
-const getFilterType = (data, tokenType = '') =>
- SPECIAL_FILTER_VALUES.includes(data) ||
- (tokenType === TOKEN_TYPE_ASSIGNEE && isPositiveInteger(data))
- ? SPECIAL_FILTER
- : NORMAL_FILTER;
+const getFilterType = (data, tokenType = '') => {
+ const isAssigneeIdParam =
+ tokenType === TOKEN_TYPE_ASSIGNEE &&
+ isPositiveInteger(data) &&
+ getParameterByName(PARAM_ASSIGNEE_ID) === data;
+
+ return specialFilterValues.includes(data) || isAssigneeIdParam ? SPECIAL_FILTER : NORMAL_FILTER;
+};
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
const isWildcardValue = (tokenType, value) =>
- wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value);
+ wildcardTokens.includes(tokenType) && specialFilterValues.includes(value);
const requiresUpperCaseValue = (tokenType, value) =>
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index 456a2029703..c664135f30e 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -1,11 +1,17 @@
<script>
-import { GlIcon, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlBadge, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
import Visibility from 'visibilityjs';
import createFlash from '~/flash';
-import { IssuableStatus, IssuableStatusText, IssuableType } from '~/issues/constants';
+import {
+ IssuableStatus,
+ IssuableStatusText,
+ WorkspaceType,
+ IssuableType,
+} from '~/issues/constants';
import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
+import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, INCIDENT_TYPE, POLLING_DELAY } from '../constants';
import eventHub from '../event_hub';
import getIssueStateQuery from '../queries/get_issue_state.query.graphql';
@@ -18,13 +24,16 @@ import PinnedLinks from './pinned_links.vue';
import titleComponent from './title.vue';
export default {
+ WorkspaceType,
components: {
GlIcon,
+ GlBadge,
GlIntersectionObserver,
titleComponent,
editedComponent,
formComponent,
PinnedLinks,
+ ConfidentialityBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -156,7 +165,7 @@ export default {
issuableType: {
type: String,
required: false,
- default: 'issue',
+ default: IssuableType.Issue,
},
canAttachFile: {
type: Boolean,
@@ -259,13 +268,19 @@ export default {
: '';
},
statusIcon() {
- return this.isClosed ? 'issue-close' : 'issue-open-m';
+ if (this.issuableType === IssuableType.Issue) {
+ return this.isClosed ? 'issue-closed' : 'issues';
+ }
+ return this.isClosed ? 'epic-closed' : 'epic';
+ },
+ statusVariant() {
+ return this.isClosed ? 'info' : 'success';
},
statusText() {
return IssuableStatusText[this.issuableStatus];
},
shouldShowStickyHeader() {
- return this.issuableType === IssuableType.Issue;
+ return [IssuableType.Issue, IssuableType.Epic].includes(this.issuableType);
},
},
created() {
@@ -327,20 +342,24 @@ export default {
});
},
- updateFormState(state) {
+ setFormState(state) {
this.store.setFormState(state);
},
- updateAndShowForm(templates = {}) {
+ updateFormState(templates = {}) {
+ this.setFormState({
+ title: this.state.titleText,
+ description: this.state.descriptionText,
+ lock_version: this.state.lock_version,
+ lockedWarningVisible: false,
+ updateLoading: false,
+ issuableTemplates: templates,
+ });
+ },
+
+ updateAndShowForm(templates) {
if (!this.showForm) {
- this.store.setFormState({
- title: this.state.titleText,
- description: this.state.descriptionText,
- lock_version: this.state.lock_version,
- lockedWarningVisible: false,
- updateLoading: false,
- issuableTemplates: templates,
- });
+ this.updateFormState(templates);
this.showForm = true;
}
},
@@ -373,9 +392,7 @@ export default {
},
updateIssuable() {
- this.store.setFormState({
- updateLoading: true,
- });
+ this.setFormState({ updateLoading: true });
const {
store: { formState },
@@ -413,10 +430,6 @@ export default {
.catch((error = {}) => {
const { message, response = {} } = error;
- this.store.setFormState({
- updateLoading: false,
- });
-
let errMsg = this.defaultErrorMessage;
if (response.data && response.data.errors) {
@@ -428,6 +441,9 @@ export default {
this.flashContainer = createFlash({
message: errMsg,
});
+ })
+ .finally(() => {
+ this.setFormState({ updateLoading: false });
});
},
@@ -446,6 +462,12 @@ export default {
}
},
+ handleListItemReorder(description) {
+ this.updateFormState();
+ this.setFormState({ description });
+ this.updateIssuable();
+ },
+
taskListUpdateStarted() {
this.poll.stop();
},
@@ -483,7 +505,7 @@ export default {
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
:issuable-type="issuableType"
- @updateForm="updateFormState"
+ @updateForm="setFormState"
/>
</div>
<div v-else>
@@ -509,19 +531,21 @@ export default {
<div
class="issue-sticky-header-text gl-display-flex gl-align-items-center gl-mx-auto gl-px-5"
>
- <p
- class="issuable-status-box status-box gl-my-0"
- :class="[isClosed ? 'status-box-issue-closed' : 'status-box-open']"
+ <gl-badge :variant="statusVariant" class="gl-mr-2">
+ <gl-icon :name="statusIcon" />
+ <span class="gl-display-none gl-sm-display-block gl-ml-2">{{
+ statusText
+ }}</span></gl-badge
>
- <gl-icon :name="statusIcon" class="gl-display-block d-sm-none gl-h-6!" />
- <span class="gl-display-none d-sm-block">{{ statusText }}</span>
- </p>
<span v-if="isLocked" data-testid="locked" class="issuable-warning-icon">
<gl-icon name="lock" :aria-label="__('Locked')" />
</span>
- <span v-if="isConfidential" data-testid="confidential" class="issuable-warning-icon">
- <gl-icon name="eye-slash" :aria-label="__('Confidential')" />
- </span>
+ <confidentiality-badge
+ v-if="isConfidential"
+ data-testid="confidential"
+ :workspace-type="$options.WorkspaceType.project"
+ :issuable-type="issuableType"
+ />
<span
v-if="isHidden"
v-gl-tooltip
@@ -559,6 +583,8 @@ export default {
:issuable-type="issuableType"
:update-url="updateEndpoint"
:lock-version="state.lock_version"
+ :is-updating="formState.updateLoading"
+ @listItemReorder="handleListItemReorder"
@taskListUpdateStarted="taskListUpdateStarted"
@taskListUpdateSucceeded="taskListUpdateSucceeded"
@taskListUpdateFailed="taskListUpdateFailed"
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 0b7e128c47b..4f97458dcd1 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -7,22 +7,31 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import $ from 'jquery';
+import Sortable from 'sortablejs';
import Vue from 'vue';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
import createFlash from '~/flash';
import { isPositiveInteger } from '~/lib/utils/number_utils';
import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
+import { getSortableDefaultOptions, isDragging } from '~/sortable/utils';
import TaskList from '~/task_list';
import Tracking from '~/tracking';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import animateMixin from '../mixins/animate';
+import { convertDescriptionWithNewSort } from '../utils';
Vue.use(GlToast);
+const workItemTypes = {
+ TASK: 'task',
+};
+
export default {
directives: {
SafeHtml,
@@ -74,6 +83,11 @@ export default {
required: false,
default: null,
},
+ isUpdating: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const workItemId = getParameterByName('work_item_id');
@@ -89,10 +103,20 @@ export default {
: undefined,
};
},
- computed: {
- showWorkItemDetailModal() {
- return Boolean(this.workItemId);
+ apollo: {
+ workItem: {
+ query: workItemQuery,
+ variables() {
+ return {
+ id: this.workItemId,
+ };
+ },
+ skip() {
+ return !this.workItemId || !this.workItemsEnabled;
+ },
},
+ },
+ computed: {
workItemsEnabled() {
return this.glFeatures.workItems;
},
@@ -126,6 +150,16 @@ export default {
if (this.workItemsEnabled) {
this.renderTaskActions();
}
+
+ if (this.workItemId) {
+ const taskLink = this.$el.querySelector(
+ `.gfm-issue[data-issue="${getIdFromGraphQLId(this.workItemId)}"]`,
+ );
+ this.openWorkItemDetailModal(taskLink);
+ }
+ },
+ beforeDestroy() {
+ this.removeAllPointerEventListeners();
},
methods: {
renderGFM() {
@@ -142,9 +176,67 @@ export default {
onSuccess: this.taskListUpdateSuccess.bind(this),
onError: this.taskListUpdateError.bind(this),
});
+
+ this.renderSortableLists();
}
},
+ renderSortableLists() {
+ this.removeAllPointerEventListeners();
+
+ const lists = document.querySelectorAll('.description ul, .description ol');
+ lists.forEach((list) => {
+ Array.from(list.children).forEach((listItem) => {
+ listItem.prepend(this.createDragIconElement());
+ this.addPointerEventListeners(listItem);
+ });
+ Sortable.create(
+ list,
+ getSortableDefaultOptions({
+ handle: '.drag-icon',
+ onUpdate: (event) => {
+ const description = convertDescriptionWithNewSort(this.descriptionText, event.to);
+ this.$emit('listItemReorder', description);
+ },
+ }),
+ );
+ });
+ },
+ createDragIconElement() {
+ const container = document.createElement('div');
+ container.innerHTML = `<svg class="drag-icon s14 gl-icon gl-cursor-grab gl-visibility-hidden" role="img" aria-hidden="true">
+ <use href="${gon.sprite_icons}#drag-vertical"></use>
+ </svg>`;
+ return container.firstChild;
+ },
+ addPointerEventListeners(listItem) {
+ const pointeroverListener = (event) => {
+ if (isDragging() || this.isUpdating) {
+ return;
+ }
+ event.target.closest('li').querySelector('.drag-icon').style.visibility = 'visible'; // eslint-disable-line no-param-reassign
+ };
+ const pointeroutListener = (event) => {
+ event.target.closest('li').querySelector('.drag-icon').style.visibility = 'hidden'; // eslint-disable-line no-param-reassign
+ };
+
+ // We use pointerover/pointerout instead of CSS so that when we hover over a
+ // list item with children, the drag icons of its children do not become visible.
+ listItem.addEventListener('pointerover', pointeroverListener);
+ listItem.addEventListener('pointerout', pointeroutListener);
+
+ this.pointerEventListeners = this.pointerEventListeners || new Map();
+ this.pointerEventListeners.set(listItem, [
+ { type: 'pointerover', listener: pointeroverListener },
+ { type: 'pointerout', listener: pointeroutListener },
+ ]);
+ },
+ removeAllPointerEventListeners() {
+ this.pointerEventListeners?.forEach((events, listItem) => {
+ events.forEach((event) => listItem.removeEventListener(event.type, event.listener));
+ this.pointerEventListeners.delete(listItem);
+ });
+ },
taskListUpdateStarted() {
this.$emit('taskListUpdateStarted');
},
@@ -195,10 +287,16 @@ export default {
taskListFields.forEach((item, index) => {
const taskLink = item.querySelector('.gfm-issue');
if (taskLink) {
- const { issue, referenceType } = taskLink.dataset;
+ const { issue, referenceType, issueType } = taskLink.dataset;
+ if (issueType !== workItemTypes.TASK) {
+ return;
+ }
+ const workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
+ this.addHoverListeners(taskLink, workItemId);
taskLink.addEventListener('click', (e) => {
e.preventDefault();
- this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
+ this.openWorkItemDetailModal(taskLink);
+ this.workItemId = workItemId;
this.updateWorkItemIdUrlQuery(issue);
this.track('viewed_work_item_from_modal', {
category: 'workItems:show',
@@ -215,10 +313,9 @@ export default {
'btn-md',
'gl-button',
'btn-default-tertiary',
- 'gl-left-0',
'gl-p-0!',
- 'gl-top-2',
- 'gl-absolute',
+ 'gl-mt-n1',
+ 'gl-ml-3',
'js-add-task',
);
button.id = `js-task-button-${index}`;
@@ -229,24 +326,46 @@ export default {
</svg>
`;
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
- button.addEventListener('click', () => this.openCreateTaskModal(button.id));
- item.prepend(button);
+ button.addEventListener('click', () => this.openCreateTaskModal(button));
+ item.append(button);
});
},
- openCreateTaskModal(id) {
- const { parentElement } = this.$el.querySelector(`#${id}`);
+ addHoverListeners(taskLink, id) {
+ let workItemPrefetch;
+ taskLink.addEventListener('mouseover', () => {
+ workItemPrefetch = setTimeout(() => {
+ this.workItemId = id;
+ }, 150);
+ });
+ taskLink.addEventListener('mouseout', () => {
+ if (workItemPrefetch) {
+ clearTimeout(workItemPrefetch);
+ }
+ });
+ },
+ setActiveTask(el) {
+ const { parentElement } = el;
const lineNumbers = parentElement.getAttribute('data-sourcepos').match(/\b\d+(?=:)/g);
this.activeTask = {
- id,
title: parentElement.innerText,
lineNumberStart: lineNumbers[0],
lineNumberEnd: lineNumbers[1],
};
+ },
+ openCreateTaskModal(el) {
+ this.setActiveTask(el);
this.$refs.modal.show();
},
closeCreateTaskModal() {
this.$refs.modal.hide();
},
+ openWorkItemDetailModal(el) {
+ if (!el) {
+ return;
+ }
+ this.setActiveTask(el);
+ this.$refs.detailsModal.show();
+ },
closeWorkItemDetailModal() {
this.workItemId = undefined;
this.updateWorkItemIdUrlQuery(undefined);
@@ -255,7 +374,8 @@ export default {
this.$emit('updateDescription', description);
this.closeCreateTaskModal();
},
- handleDeleteTask() {
+ handleDeleteTask(description) {
+ this.$emit('updateDescription', description);
this.$toast.show(s__('WorkItem|Work item deleted'));
},
updateWorkItemIdUrlQuery(workItemId) {
@@ -318,9 +438,13 @@ export default {
/>
</gl-modal>
<work-item-detail-modal
+ ref="detailsModal"
:can-update="canUpdate"
- :visible="showWorkItemDetailModal"
:work-item-id="workItemId"
+ :issue-gid="issueGid"
+ :lock-version="lockVersion"
+ :line-number-start="activeTask.lineNumberStart"
+ :line-number-end="activeTask.lineNumberEnd"
@workItemDeleted="handleDeleteTask"
@close="closeWorkItemDetailModal"
/>
diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js
index 4a5ebf9615b..6b0b26ef2e3 100644
--- a/app/assets/javascripts/issues/show/index.js
+++ b/app/assets/javascripts/issues/show/index.js
@@ -38,6 +38,7 @@ export function initIncidentApp(issueData = {}) {
projectId,
slaFeatureAvailable,
uploadMetricsFeatureAvailable,
+ state,
} = issueData;
const fullPath = `${projectNamespace}/${projectPath}`;
@@ -60,6 +61,7 @@ export function initIncidentApp(issueData = {}) {
return createElement(IssueApp, {
props: {
...issueData,
+ issuableStatus: state,
descriptionComponent: IncidentTabs,
showTitleBorder: false,
},
diff --git a/app/assets/javascripts/issues/show/utils.js b/app/assets/javascripts/issues/show/utils.js
new file mode 100644
index 00000000000..60e66f59f92
--- /dev/null
+++ b/app/assets/javascripts/issues/show/utils.js
@@ -0,0 +1,99 @@
+import { COLON, HYPHEN, NEWLINE } from '~/lib/utils/text_utility';
+
+/**
+ * Get the index from sourcepos that represents the line of
+ * the description when the description is split by newline.
+ *
+ * @param {String} sourcepos Source position in format `23:3-23:14`
+ * @returns {Number} Index of description split by newline
+ */
+const getDescriptionIndex = (sourcepos) => {
+ const [startRange] = sourcepos.split(HYPHEN);
+ const [startRow] = startRange.split(COLON);
+ return startRow - 1;
+};
+
+/**
+ * Given a `ul` or `ol` element containing a new sort order, this function performs
+ * a depth-first search to get the new sort order in the form of sourcepos indices.
+ *
+ * @param {HTMLElement} list A `ul` or `ol` element containing a new sort order
+ * @returns {Array<Number>} An array representing the new order of the list
+ */
+const getNewSourcePositions = (list) => {
+ const newSourcePositions = [];
+
+ function pushPositionOfChildListItems(el) {
+ if (!el) {
+ return;
+ }
+ if (el.tagName === 'LI') {
+ newSourcePositions.push(getDescriptionIndex(el.dataset.sourcepos));
+ }
+ Array.from(el.children).forEach(pushPositionOfChildListItems);
+ }
+
+ pushPositionOfChildListItems(list);
+
+ return newSourcePositions;
+};
+
+/**
+ * Converts a description to one with a new list sort order.
+ *
+ * Given a description like:
+ *
+ * <pre>
+ * 1. I am text
+ * 2.
+ * 3. - Item 1
+ * 4. - Item 2
+ * 5. - Item 3
+ * 6. - Item 4
+ * 7. - Item 5
+ * </pre>
+ *
+ * And a reordered list (due to dragging Item 2 into Item 1's position) like:
+ *
+ * <pre>
+ * <ul data-sourcepos="3:1-8:0">
+ * <li data-sourcepos="4:1-4:8">
+ * Item 2
+ * <ul data-sourcepos="5:1-6:10">
+ * <li data-sourcepos="5:1-5:10">Item 3</li>
+ * <li data-sourcepos="6:1-6:10">Item 4</li>
+ * </ul>
+ * </li>
+ * <li data-sourcepos="3:1-3:8">Item 1</li>
+ * <li data-sourcepos="7:1-8:0">Item 5</li>
+ * <ul>
+ * </pre>
+ *
+ * This function returns:
+ *
+ * <pre>
+ * 1. I am text
+ * 2.
+ * 3. - Item 2
+ * 4. - Item 3
+ * 5. - Item 4
+ * 6. - Item 1
+ * 7. - Item 5
+ * </pre>
+ *
+ * @param {String} description Description in markdown format
+ * @param {HTMLElement} list A `ul` or `ol` element containing a new sort order
+ * @returns {String} Markdown with a new list sort order
+ */
+export const convertDescriptionWithNewSort = (description, list) => {
+ const descriptionLines = description.split(NEWLINE);
+ const startIndexOfList = getDescriptionIndex(list.dataset.sourcepos);
+
+ getNewSourcePositions(list)
+ .map((lineIndex) => descriptionLines[lineIndex])
+ .forEach((line, index) => {
+ descriptionLines[startIndexOfList + index] = line;
+ });
+
+ return descriptionLines.join(NEWLINE);
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/api.js b/app/assets/javascripts/jira_connect/subscriptions/api.js
index 14947b6c835..de67703356f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/api.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/api.js
@@ -29,3 +29,13 @@ export const fetchGroups = async (groupsPath, { page, perPage, search }) => {
},
});
};
+
+export const fetchSubscriptions = async (subscriptionsPath) => {
+ const jwt = await getJwt();
+
+ return axios.get(subscriptionsPath, {
+ params: {
+ jwt,
+ },
+ });
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
index 1fc40e5c0d6..fa1c2e1912c 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
@@ -1,17 +1,27 @@
<script>
+import { mapActions } from 'vuex';
import { GlButton } from '@gitlab/ui';
-import { helpPagePath } from '~/helpers/help_page_helper';
import { addSubscription } from '~/jira_connect/subscriptions/api';
import { persistAlert, reloadPage } from '~/jira_connect/subscriptions/utils';
-import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import GroupItemName from '../group_item_name.vue';
+import {
+ INTEGRATIONS_DOC_LINK,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE,
+} from '../../constants';
export default {
components: {
GlButton,
GroupItemName,
},
+ mixins: [glFeatureFlagMixin()],
inject: {
+ addSubscriptionsPath: {
+ default: '',
+ },
subscriptionsPath: {
default: '',
},
@@ -32,31 +42,41 @@ export default {
isLoading: false,
};
},
+ computed: {
+ oauthEnabled() {
+ return this.glFeatures.jiraConnectOauth;
+ },
+ },
methods: {
- onClick() {
+ ...mapActions(['addSubscription']),
+ async onClick() {
+ if (this.oauthEnabled) {
+ this.isLoading = true;
+ await this.addSubscription({
+ namespacePath: this.group.full_path,
+ subscriptionsPath: this.subscriptionsPath,
+ });
+ this.isLoading = false;
+ } else {
+ this.deprecatedAddSubscription();
+ }
+ },
+ deprecatedAddSubscription() {
this.isLoading = true;
- addSubscription(this.subscriptionsPath, this.group.full_path)
+ addSubscription(this.addSubscriptionsPath, this.group.full_path)
.then(() => {
persistAlert({
- title: s__('Integrations|Namespace successfully linked'),
- message: s__(
- 'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
- ),
- linkUrl: helpPagePath('integration/jira_development_panel.html', {
- anchor: 'use-the-integration',
- }),
+ title: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ message: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ linkUrl: INTEGRATIONS_DOC_LINK,
variant: 'success',
});
reloadPage();
})
.catch((error) => {
- this.$emit(
- 'error',
- error?.response?.data?.error ||
- s__('Integrations|Failed to link namespace. Please try again.'),
- );
+ this.$emit('error', error?.response?.data?.error || I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE);
this.isLoading = false;
});
},
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 51db3e784aa..22422872183 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -1,14 +1,14 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapState, mapMutations } from 'vuex';
+import { mapState, mapMutations, mapActions } from 'vuex';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import AccessorUtilities from '~/lib/utils/accessor';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE } from '../constants';
import { SET_ALERT } from '../store/mutation_types';
-import SignInPage from '../pages/sign_in.vue';
-import SubscriptionsPage from '../pages/subscriptions.vue';
+import SignInPage from '../pages/sign_in/sign_in_page.vue';
+import SubscriptionsPage from '../pages/subscriptions_page.vue';
import UserLink from './user_link.vue';
import CompatibilityAlert from './compatibility_alert.vue';
import BrowserSupportAlert from './browser_support_alert.vue';
@@ -30,17 +30,13 @@ export default {
usersPath: {
default: '',
},
- subscriptions: {
- default: [],
+ subscriptionsPath: {
+ default: '',
},
},
- data() {
- return {
- user: null,
- };
- },
computed: {
- ...mapState(['alert']),
+ ...mapState(['currentUser']),
+ ...mapState(['alert', 'subscriptions']),
shouldShowAlert() {
return Boolean(this.alert?.message);
},
@@ -48,7 +44,11 @@ export default {
return !isEmpty(this.subscriptions);
},
userSignedIn() {
- return Boolean(!this.usersPath || this.user);
+ if (this.isOauthEnabled) {
+ return Boolean(this.currentUser);
+ }
+
+ return Boolean(!this.usersPath);
},
isOauthEnabled() {
return this.glFeatures.jiraConnectOauth;
@@ -64,16 +64,29 @@ export default {
created() {
this.setInitialAlert();
},
+ mounted() {
+ this.fetchSubscriptionsOauth();
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
}),
+ ...mapActions(['fetchSubscriptions']),
+ /**
+ * Fetch subscriptions from the REST API,
+ * if the jiraConnectOauth flag is enabled.
+ */
+ fetchSubscriptionsOauth() {
+ if (!this.isOauthEnabled) return;
+
+ this.fetchSubscriptions(this.subscriptionsPath);
+ },
setInitialAlert() {
const { linkUrl, title, message, variant } = retrieveAlert() || {};
this.setAlert({ linkUrl, title, message, variant });
},
- onSignInOauth(user) {
- this.user = user;
+ onSignInOauth() {
+ this.fetchSubscriptionsOauth();
},
onSignInError() {
this.setAlert({
@@ -109,9 +122,12 @@ export default {
</template>
</gl-alert>
- <user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" :user="user" />
+ <user-link
+ :user-signed-in="userSignedIn"
+ :has-subscriptions="hasSubscriptions"
+ :user="currentUser"
+ />
- <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
<div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
<sign-in-page
v-if="!userSignedIn"
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
index dfed57df7d6..b9e8bab019f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions, mapMutations } from 'vuex';
import { GlButton } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import {
@@ -8,8 +9,8 @@ import {
} from '~/jira_connect/subscriptions/constants';
import { setUrlParams } from '~/lib/utils/url_utility';
import AccessorUtilities from '~/lib/utils/accessor';
-
import { createCodeVerifier, createCodeChallenge } from '../pkce';
+import { SET_ACCESS_TOKEN } from '../store/mutation_types';
export default {
components: {
@@ -31,6 +32,10 @@ export default {
window.removeEventListener('message', this.handleWindowMessage);
},
methods: {
+ ...mapActions(['loadCurrentUser']),
+ ...mapMutations({
+ setAccessToken: SET_ACCESS_TOKEN,
+ }),
async startOAuthFlow() {
this.loading = true;
@@ -40,6 +45,7 @@ export default {
// Build the initial OAuth authorization URL
const { oauth_authorize_url: oauthAuthorizeURL } = this.oauthMetadata;
+
const oauthAuthorizeURLWithChallenge = setUrlParams(
{
code_challenge: codeChallenge,
@@ -57,7 +63,6 @@ export default {
async handleWindowMessage(event) {
if (window.origin !== event.origin) {
this.loading = false;
- this.handleError();
return;
}
@@ -73,7 +78,10 @@ export default {
const code = event.data?.code;
try {
const accessToken = await this.getOAuthToken(code);
- await this.loadUser(accessToken);
+ await this.loadCurrentUser(accessToken);
+
+ this.setAccessToken(accessToken);
+ this.$emit('sign-in');
} catch (e) {
this.handleError();
} finally {
@@ -96,13 +104,6 @@ export default {
return data.access_token;
},
- async loadUser(accessToken) {
- const { data } = await axios.get('/api/v4/user', {
- headers: { Authorization: `Bearer ${accessToken}` },
- });
-
- this.$emit('sign-in', data);
- },
},
i18n: {
defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
index 0251728c896..4c039be9ba5 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlTableLite } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapMutations } from 'vuex';
+import { mapMutations, mapState } from 'vuex';
import { removeSubscription } from '~/jira_connect/subscriptions/api';
import { reloadPage } from '~/jira_connect/subscriptions/utils';
import { __, s__ } from '~/locale';
@@ -16,11 +16,6 @@ export default {
GroupItemName,
TimeagoTooltip,
},
- inject: {
- subscriptions: {
- default: [],
- },
- },
data() {
return {
loadingItem: null,
@@ -45,6 +40,9 @@ export default {
i18n: {
unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'),
},
+ computed: {
+ ...mapState(['subscriptions']),
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/constants.js b/app/assets/javascripts/jira_connect/subscriptions/constants.js
index d30ebdbb487..8faafb1b0d0 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/constants.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/constants.js
@@ -1,4 +1,5 @@
import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
export const DEFAULT_GROUPS_PER_PAGE = 10;
export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert';
@@ -8,6 +9,22 @@ export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal';
export const I18N_DEFAULT_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to GitLab');
export const I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE = s__('Integrations|Failed to sign in to GitLab.');
+export const I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
+ 'Integrations|Failed to load subscriptions.',
+);
+export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE = s__(
+ 'Integrations|Namespace successfully linked',
+);
+export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE = s__(
+ 'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
+);
+export const INTEGRATIONS_DOC_LINK = helpPagePath('integration/jira_development_panel', {
+ anchor: 'use-the-integration',
+});
+
+export const I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
+ 'Integrations|Failed to link namespace. Please try again.',
+);
const OAUTH_WINDOW_SIZE = 800;
export const OAUTH_WINDOW_OPTIONS = [
diff --git a/app/assets/javascripts/jira_connect/subscriptions/index.js b/app/assets/javascripts/jira_connect/subscriptions/index.js
index 3b584b5fe98..8e9f73538b9 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/index.js
@@ -9,8 +9,6 @@ import JiraConnectApp from './components/app.vue';
import createStore from './store';
import { sizeToParent } from './utils';
-const store = createStore();
-
export function initJiraConnect() {
const el = document.querySelector('.js-jira-connect-app');
if (!el) {
@@ -24,6 +22,7 @@ export function initJiraConnect() {
const {
groupsPath,
subscriptions,
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
@@ -31,12 +30,14 @@ export function initJiraConnect() {
} = el.dataset;
sizeToParent();
+ const store = createStore({ subscriptions: JSON.parse(subscriptions) });
+
return new Vue({
el,
store,
provide: {
groupsPath,
- subscriptions: JSON.parse(subscriptions),
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
deleted file mode 100644
index a24ee33b723..00000000000
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import { s__ } from '~/locale';
-
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import SubscriptionsList from '../components/subscriptions_list.vue';
-
-export default {
- name: 'SignInPage',
- components: {
- SubscriptionsList,
- SignInLegacyButton: () => import('../components/sign_in_legacy_button.vue'),
- SignInOauthButton: () => import('../components/sign_in_oauth_button.vue'),
- },
- mixins: [glFeatureFlagMixin()],
- inject: ['usersPath'],
- props: {
- hasSubscriptions: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- useSignInOauthButton() {
- return this.glFeatures.jiraConnectOauth;
- },
- },
- i18n: {
- signInButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
- signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
- },
- methods: {
- onSignInError() {
- this.$emit('error');
- },
- },
-};
-</script>
-
-<template>
- <div v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
- <sign-in-oauth-button
- v-if="useSignInOauthButton"
- @sign-in="$emit('sign-in-oauth', $event)"
- @error="onSignInError"
- >
- {{ $options.i18n.signInButtonTextWithSubscriptions }}
- </sign-in-oauth-button>
- <sign-in-legacy-button v-else :users-path="usersPath">
- {{ $options.i18n.signInButtonTextWithSubscriptions }}
- </sign-in-legacy-button>
- </div>
-
- <subscriptions-list />
- </div>
- <div v-else class="gl-text-center">
- <p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
- <sign-in-oauth-button
- v-if="useSignInOauthButton"
- @sign-in="$emit('sign-in-oauth', $event)"
- @error="onSignInError"
- />
- <sign-in-legacy-button v-else class="gl-mb-7" :users-path="usersPath" />
- </div>
-</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
new file mode 100644
index 00000000000..91b66c87694
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
@@ -0,0 +1,68 @@
+<script>
+import { s__ } from '~/locale';
+
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SubscriptionsList from '../../components/subscriptions_list.vue';
+
+export default {
+ name: 'SignInGitlabCom',
+ components: {
+ SubscriptionsList,
+ SignInLegacyButton: () => import('../../components/sign_in_legacy_button.vue'),
+ SignInOauthButton: () => import('../../components/sign_in_oauth_button.vue'),
+ },
+ mixins: [glFeatureFlagMixin()],
+ inject: ['usersPath'],
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ useSignInOauthButton() {
+ return this.glFeatures.jiraConnectOauth;
+ },
+ },
+ i18n: {
+ signInButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
+ signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
+ },
+ methods: {
+ onSignInError() {
+ this.$emit('error');
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
+ <div v-if="hasSubscriptions">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
+ <sign-in-oauth-button
+ v-if="useSignInOauthButton"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ >
+ {{ $options.i18n.signInButtonTextWithSubscriptions }}
+ </sign-in-oauth-button>
+ <sign-in-legacy-button v-else :users-path="usersPath">
+ {{ $options.i18n.signInButtonTextWithSubscriptions }}
+ </sign-in-legacy-button>
+ </div>
+
+ <subscriptions-list />
+ </div>
+ <div v-else class="gl-text-center">
+ <p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
+ <sign-in-oauth-button
+ v-if="useSignInOauthButton"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ />
+ <sign-in-legacy-button v-else class="gl-mb-7" :users-path="usersPath" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
new file mode 100644
index 00000000000..4f5aa4c255c
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
@@ -0,0 +1,72 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import SignInOauthButton from '../../../components/sign_in_oauth_button.vue';
+import VersionSelectForm from './version_select_form.vue';
+
+export default {
+ name: 'SignInGitlabMultiversion',
+ components: {
+ GlButton,
+ SignInOauthButton,
+ VersionSelectForm,
+ },
+ data() {
+ return {
+ gitlabBasePath: null,
+ };
+ },
+ computed: {
+ hasSelectedVersion() {
+ return this.gitlabBasePath !== null;
+ },
+ subtitle() {
+ return this.hasSelectedVersion
+ ? this.$options.i18n.signInSubtitle
+ : this.$options.i18n.versionSelectSubtitle;
+ },
+ },
+ methods: {
+ resetGitlabBasePath() {
+ this.gitlabBasePath = null;
+ },
+ onVersionSelect(gitlabBasePath) {
+ this.gitlabBasePath = gitlabBasePath;
+ },
+ onSignInError() {
+ this.$emit('error');
+ },
+ },
+ i18n: {
+ title: s__('JiraService|Welcome to GitLab for Jira'),
+ signInSubtitle: s__('JiraService|Sign in to GitLab to link namespaces.'),
+ versionSelectSubtitle: s__('JiraService|What version of GitLab are you using?'),
+ changeVersionButtonText: s__('JiraService|Change GitLab version'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-text-center">
+ <h2>{{ $options.i18n.title }}</h2>
+ <p data-testid="subtitle">{{ subtitle }}</p>
+ </div>
+
+ <version-select-form v-if="!hasSelectedVersion" class="gl-mt-7" @submit="onVersionSelect" />
+
+ <div v-else class="gl-text-center">
+ <sign-in-oauth-button
+ class="gl-mb-5"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ />
+
+ <div>
+ <gl-button category="tertiary" variant="confirm" @click="resetGitlabBasePath">
+ {{ $options.i18n.changeVersionButtonText }}
+ </gl-button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
new file mode 100644
index 00000000000..0fa745ed7e3
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
@@ -0,0 +1,88 @@
+<script>
+import {
+ GlForm,
+ GlFormGroup,
+ GlFormRadioGroup,
+ GlFormInput,
+ GlFormRadio,
+ GlButton,
+} from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+const RADIO_OPTIONS = {
+ saas: 'saas',
+ selfManaged: 'selfManaged',
+};
+
+const DEFAULT_RADIO_OPTION = RADIO_OPTIONS.saas;
+const GITLAB_COM_BASE_PATH = 'https://gitlab.com';
+
+export default {
+ name: 'VersionSelectForm',
+ components: {
+ GlForm,
+ GlFormGroup,
+ GlFormRadioGroup,
+ GlFormInput,
+ GlFormRadio,
+ GlButton,
+ },
+ data() {
+ return {
+ selected: DEFAULT_RADIO_OPTION,
+ selfManagedBasePathInput: '',
+ };
+ },
+ computed: {
+ isSelfManagedSelected() {
+ return this.selected === RADIO_OPTIONS.selfManaged;
+ },
+ },
+ methods: {
+ onSubmit() {
+ const gitlabBasePath =
+ this.selected === RADIO_OPTIONS.saas ? GITLAB_COM_BASE_PATH : this.selfManagedBasePathInput;
+ this.$emit('submit', gitlabBasePath);
+ },
+ },
+ radioOptions: RADIO_OPTIONS,
+ i18n: {
+ title: s__('JiraService|Welcome to GitLab for Jira'),
+ saasRadioLabel: __('GitLab.com (SaaS)'),
+ saasRadioHelp: __('Most common'),
+ selfManagedRadioLabel: __('GitLab (self-managed)'),
+ instanceURLInputLabel: s__('JiraService|GitLab instance URL'),
+ instanceURLInputDescription: s__('JiraService|For example: https://gitlab.example.com'),
+ },
+};
+</script>
+
+<template>
+ <gl-form class="gl-max-w-62 gl-mx-auto" @submit.prevent="onSubmit">
+ <gl-form-radio-group v-model="selected" class="gl-mb-3" name="gitlab_version">
+ <gl-form-radio :value="$options.radioOptions.saas">
+ {{ $options.i18n.saasRadioLabel }}
+ <template #help>
+ {{ $options.i18n.saasRadioHelp }}
+ </template>
+ </gl-form-radio>
+ <gl-form-radio :value="$options.radioOptions.selfManaged">
+ {{ $options.i18n.selfManagedRadioLabel }}
+ </gl-form-radio>
+ </gl-form-radio-group>
+
+ <gl-form-group
+ v-if="isSelfManagedSelected"
+ class="gl-ml-6"
+ :label="$options.i18n.instanceURLInputLabel"
+ :description="$options.i18n.instanceURLInputDescription"
+ label-for="self-managed-instance-input"
+ >
+ <gl-form-input id="self-managed-instance-input" v-model="selfManagedBasePathInput" required />
+ </gl-form-group>
+
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button variant="confirm" type="submit">{{ __('Save') }}</gl-button>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue
new file mode 100644
index 00000000000..f4c59b2184e
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue
@@ -0,0 +1,35 @@
+<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SignInGitlabCom from './sign_in_gitlab_com.vue';
+import SignInGitlabMultiversion from './sign_in_gitlab_multiversion/index.vue';
+
+export default {
+ name: 'SignInPage',
+ components: { SignInGitlabCom, SignInGitlabMultiversion },
+ mixins: [glFeatureFlagMixin()],
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ isOauthSelfManagedEnabled() {
+ return this.glFeatures.jiraConnectOauth && this.glFeatures.jiraConnectOauthSelfManaged;
+ },
+ },
+};
+</script>
+<template>
+ <sign-in-gitlab-multiversion
+ v-if="isOauthSelfManagedEnabled"
+ @sign-in-oauth="$emit('sign-in-oauth', $event)"
+ @error="$emit('error', $event)"
+ />
+ <sign-in-gitlab-com
+ v-else
+ :has-subscriptions="hasSubscriptions"
+ @sign-in-oauth="$emit('sign-in-oauth')"
+ @error="$emit('error', $event)"
+ />
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
deleted file mode 100644
index 426f2999370..00000000000
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<script>
-import { GlEmptyState } from '@gitlab/ui';
-import SubscriptionsList from '../components/subscriptions_list.vue';
-import AddNamespaceButton from '../components/add_namespace_button.vue';
-
-export default {
- name: 'SubscriptionsPage',
- components: {
- GlEmptyState,
- SubscriptionsList,
- AddNamespaceButton,
- },
- props: {
- hasSubscriptions: {
- type: Boolean,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
- <add-namespace-button />
- </div>
-
- <subscriptions-list />
- </div>
- <gl-empty-state
- v-else
- :title="s__('Integrations|No linked namespaces')"
- :description="
- s__(
- 'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
- )
- "
- >
- <template #actions>
- <add-namespace-button />
- </template>
- </gl-empty-state>
-</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
new file mode 100644
index 00000000000..b1c1ae73e14
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
@@ -0,0 +1,54 @@
+<script>
+import { mapState } from 'vuex';
+import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+
+import SubscriptionsList from '../components/subscriptions_list.vue';
+import AddNamespaceButton from '../components/add_namespace_button.vue';
+
+export default {
+ name: 'SubscriptionsPage',
+ components: {
+ GlEmptyState,
+ GlLoadingIcon,
+ SubscriptionsList,
+ AddNamespaceButton,
+ },
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['subscriptionsLoading', 'subscriptionsError']),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
+
+ <gl-loading-icon v-if="subscriptionsLoading" size="md" />
+ <div v-else-if="hasSubscriptions && !subscriptionsError">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
+ <add-namespace-button />
+ </div>
+
+ <subscriptions-list />
+ </div>
+ <gl-empty-state
+ v-else
+ :title="s__('Integrations|No linked namespaces')"
+ :description="
+ s__(
+ 'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
+ )
+ "
+ >
+ <template #actions>
+ <add-namespace-button />
+ </template>
+ </gl-empty-state>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/actions.js b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
new file mode 100644
index 00000000000..4a83ee8671d
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
@@ -0,0 +1,73 @@
+import { fetchSubscriptions as fetchSubscriptionsREST } from '~/jira_connect/subscriptions/api';
+import { getCurrentUser } from '~/rest_api';
+import { addJiraConnectSubscription } from '~/api/integrations_api';
+import {
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ INTEGRATIONS_DOC_LINK,
+ I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE,
+} from '../constants';
+import { getJwt } from '../utils';
+import {
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+ ADD_SUBSCRIPTION_LOADING,
+ ADD_SUBSCRIPTION_ERROR,
+ SET_ALERT,
+ SET_CURRENT_USER,
+ SET_CURRENT_USER_ERROR,
+} from './mutation_types';
+
+export const fetchSubscriptions = async ({ commit }, subscriptionsPath) => {
+ commit(SET_SUBSCRIPTIONS_LOADING, true);
+
+ try {
+ const data = await fetchSubscriptionsREST(subscriptionsPath);
+ commit(SET_SUBSCRIPTIONS, data.data.subscriptions);
+ } catch {
+ commit(SET_SUBSCRIPTIONS_ERROR, true);
+ commit(SET_ALERT, { message: I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE, variant: 'danger' });
+ } finally {
+ commit(SET_SUBSCRIPTIONS_LOADING, false);
+ }
+};
+
+export const loadCurrentUser = async ({ commit }, accessToken) => {
+ try {
+ const { data: user } = await getCurrentUser({
+ headers: { Authorization: `Bearer ${accessToken}` },
+ });
+
+ commit(SET_CURRENT_USER, user);
+ } catch (e) {
+ commit(SET_CURRENT_USER_ERROR, e);
+ }
+};
+
+export const addSubscription = async (
+ { commit, state, dispatch },
+ { namespacePath, subscriptionsPath },
+) => {
+ try {
+ commit(ADD_SUBSCRIPTION_LOADING, true);
+
+ await addJiraConnectSubscription(namespacePath, {
+ jwt: await getJwt(),
+ accessToken: state.accessToken,
+ });
+
+ commit(SET_ALERT, {
+ title: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ message: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ linkUrl: INTEGRATIONS_DOC_LINK,
+ variant: 'success',
+ });
+
+ dispatch('fetchSubscriptions', subscriptionsPath);
+ } catch (e) {
+ commit(ADD_SUBSCRIPTION_ERROR, e);
+ } finally {
+ commit(ADD_SUBSCRIPTION_LOADING, false);
+ }
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/index.js b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
index de830e3891a..abad1920bcc 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
@@ -1,12 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import * as actions from './actions';
import mutations from './mutations';
-import state from './state';
+import createState from './state';
Vue.use(Vuex);
-export default () =>
- new Vuex.Store({
+export default function createStore(initialState) {
+ return new Vuex.Store({
mutations,
- state,
+ actions,
+ state: createState(initialState),
});
+}
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
index 15f36b824d9..d4893fbcaf6 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
@@ -1 +1,13 @@
export const SET_ALERT = 'SET_ALERT';
+
+export const SET_SUBSCRIPTIONS = 'SET_SUBSCRIPTIONS';
+export const SET_SUBSCRIPTIONS_LOADING = 'SET_SUBSCRIPTIONS_LOADING';
+export const SET_SUBSCRIPTIONS_ERROR = 'SET_SUBSCRIPTIONS_ERROR';
+
+export const ADD_SUBSCRIPTION_LOADING = 'ADD_SUBSCRIPTION_LOADING';
+export const ADD_SUBSCRIPTION_ERROR = 'ADD_SUBSCRIPTION_ERROR';
+
+export const SET_CURRENT_USER = 'SET_CURRENT_USER';
+export const SET_CURRENT_USER_ERROR = 'SET_CURRENT_USER_ERROR';
+
+export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN';
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
index 2a25e0fe25f..60076c918fd 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
@@ -1,7 +1,45 @@
-import { SET_ALERT } from './mutation_types';
+import {
+ SET_ALERT,
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+ ADD_SUBSCRIPTION_LOADING,
+ ADD_SUBSCRIPTION_ERROR,
+ SET_CURRENT_USER,
+ SET_CURRENT_USER_ERROR,
+ SET_ACCESS_TOKEN,
+} from './mutation_types';
export default {
[SET_ALERT](state, { title, message, variant, linkUrl } = {}) {
state.alert = { title, message, variant, linkUrl };
},
+
+ [SET_SUBSCRIPTIONS](state, subscriptions = []) {
+ state.subscriptions = subscriptions;
+ },
+ [SET_SUBSCRIPTIONS_LOADING](state, subscriptionsLoading) {
+ state.subscriptionsLoading = subscriptionsLoading;
+ },
+ [SET_SUBSCRIPTIONS_ERROR](state, subscriptionsError) {
+ state.subscriptionsError = subscriptionsError;
+ },
+
+ [ADD_SUBSCRIPTION_LOADING](state, loading) {
+ state.addSubscriptionLoading = loading;
+ },
+ [ADD_SUBSCRIPTION_ERROR](state, error) {
+ state.addSubscriptionError = error;
+ },
+
+ [SET_CURRENT_USER](state, currentUser) {
+ state.currentUser = currentUser;
+ },
+ [SET_CURRENT_USER_ERROR](state, currentUserError) {
+ state.currentUserError = currentUserError;
+ },
+
+ [SET_ACCESS_TOKEN](state, accessToken) {
+ state.accessToken = accessToken;
+ },
};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/state.js b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
index c807df03f00..03a83f18b4c 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/state.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
@@ -1,3 +1,17 @@
-export default () => ({
- alert: undefined,
-});
+export default function createState({ subscriptions = [], subscriptionsLoading = false } = {}) {
+ return {
+ alert: undefined,
+
+ subscriptions,
+ subscriptionsLoading,
+ subscriptionsError: false,
+
+ addSubscriptionLoading: false,
+ addSubscriptionError: false,
+
+ currentUser: null,
+ currentUserError: null,
+
+ accessToken: null,
+ };
+}
diff --git a/app/assets/javascripts/jira_import/components/jira_import_form.vue b/app/assets/javascripts/jira_import/components/jira_import_form.vue
index af4a26a7352..8a36a4d2466 100644
--- a/app/assets/javascripts/jira_import/components/jira_import_form.vue
+++ b/app/assets/javascripts/jira_import/components/jira_import_form.vue
@@ -304,7 +304,10 @@ export default {
:text="data.value || $options.currentUsername"
class="w-100"
:aria-label="
- sprintf($options.dropdownLabel, { jiraDisplayName: data.item.jiraDisplayName })
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf($options.dropdownLabel, {
+ jiraDisplayName: data.item.jiraDisplayName,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
@hide="resetDropdown"
>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 85fe5ed7e26..396b015ad83 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -94,7 +94,7 @@ export default {
'emptyStateIllustration',
'isScrollingDown',
'emptyStateAction',
- 'hasRunnersForProject',
+ 'hasOfflineRunnersForProject',
]),
shouldRenderContent() {
@@ -220,7 +220,7 @@ export default {
<!-- Body Section -->
<stuck-block
v-if="job.stuck"
- :has-no-runners-for-project="hasRunnersForProject"
+ :has-offline-runners-for-project="hasOfflineRunnersForProject"
:tags="job.tags"
:runners-path="runnerSettingsUrl"
/>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 1b4c9ebdf7d..cc099dba72f 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -125,6 +125,7 @@ export default {
:title="$options.i18n.cancelJobButtonLabel"
:aria-label="$options.i18n.cancelJobButtonLabel"
:href="job.cancel_path"
+ variant="danger"
icon="cancel"
data-method="post"
data-testid="cancel-button"
diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
index 5428f657252..2ba531c9e95 100644
--- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
@@ -56,7 +56,7 @@ export default {
});
},
runnerId() {
- const { id, short_sha: token, description } = this.job?.runner;
+ const { id, short_sha: token, description } = this.job.runner;
return `#${id} (${token}) ${description}`;
},
@@ -71,11 +71,20 @@ export default {
return '';
}
- return sprintf(__(` (from %{timeoutSource})`), {
+ return sprintf(__(' (from %{timeoutSource})'), {
timeoutSource: this.job.metadata.timeout_source,
});
},
},
+ i18n: {
+ COVERAGE: __('Coverage'),
+ FINISHED: __('Finished'),
+ ERASED: __('Erased'),
+ QUEUED: __('Queued'),
+ RUNNER: __('Runner'),
+ TAGS: __('Tags:'),
+ TIMEOUT: __('Timeout'),
+ },
};
</script>
@@ -86,22 +95,22 @@ export default {
v-if="job.finished_at"
:value="finishedAt"
data-testid="job-finished"
- title="Finished"
+ :title="$options.i18n.FINISHED"
/>
- <detail-row v-if="job.erased_at" :value="erasedAt" title="Erased" />
- <detail-row v-if="job.queued" :value="queued" title="Queued" />
+ <detail-row v-if="job.erased_at" :value="erasedAt" :title="$options.i18n.ERASED" />
+ <detail-row v-if="job.queued" :value="queued" :title="$options.i18n.QUEUED" />
<detail-row
v-if="hasTimeout"
:help-url="runnerHelpUrl"
:value="timeout"
data-testid="job-timeout"
- title="Timeout"
+ :title="$options.i18n.TIMEOUT"
/>
- <detail-row v-if="job.runner" :value="runnerId" title="Runner" />
- <detail-row v-if="job.coverage" :value="coverage" title="Coverage" />
+ <detail-row v-if="job.runner" :value="runnerId" :title="$options.i18n.RUNNER" />
+ <detail-row v-if="job.coverage" :value="coverage" :title="$options.i18n.COVERAGE" />
<p v-if="hasTags" class="build-detail-row" data-testid="job-tags">
- <span class="font-weight-bold">{{ __('Tags:') }}</span>
+ <span class="font-weight-bold">{{ $options.i18n.TAGS }}</span>
<gl-badge v-for="(tag, i) in job.tags" :key="i" variant="info">{{ tag }}</gl-badge>
</p>
</div>
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
index abd0c13702a..f9cde61e917 100644
--- a/app/assets/javascripts/jobs/components/stuck_block.vue
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -11,7 +11,7 @@ export default {
GlLink,
},
props: {
- hasNoRunnersForProject: {
+ hasOfflineRunnersForProject: {
type: Boolean,
required: true,
},
@@ -37,7 +37,7 @@ export default {
dataTestId: 'job-stuck-with-tags',
showTags: true,
};
- } else if (this.hasNoRunnersForProject) {
+ } else if (this.hasOfflineRunnersForProject) {
return {
text: s__(`Job|This job is stuck because the project
doesn't have any runners online assigned to it.`),
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index f16e0287d5d..02aeb46a22b 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -1,6 +1,7 @@
<script>
import { GlButton, GlButtonGroup, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+import { redirectTo } from '~/lib/utils/url_utility';
import {
ACTIONS_DOWNLOAD_ARTIFACTS,
ACTIONS_START_NOW,
@@ -108,11 +109,11 @@ export default {
},
},
methods: {
- async postJobAction(name, mutation) {
+ async postJobAction(name, mutation, redirect = false) {
try {
const {
data: {
- [name]: { errors },
+ [name]: { errors, job },
},
} = await this.$apollo.mutate({
mutation,
@@ -121,6 +122,10 @@ export default {
if (errors.length > 0) {
reportMessageToSentry(this.$options.name, errors.join(', '), {});
this.showToastMessage();
+ } else if (redirect) {
+ // Retry and Play actions redirect to job detail view
+ // we don't need to refetch with jobActionPerformed event
+ redirectTo(job.detailedStatus.detailsPath);
} else {
eventHub.$emit('jobActionPerformed');
}
@@ -147,12 +152,12 @@ export default {
retryJob() {
this.retryBtnDisabled = true;
- this.postJobAction(this.$options.jobRetry, retryJobMutation);
+ this.postJobAction(this.$options.jobRetry, retryJobMutation, true);
},
playJob() {
this.playManualBtnDisabled = true;
- this.postJobAction(this.$options.jobPlay, playJobMutation);
+ this.postJobAction(this.$options.jobPlay, playJobMutation, true);
},
unscheduleJob() {
this.unscheduleBtnDisabled = true;
diff --git a/app/assets/javascripts/jobs/components/table/graphql/fragments/job.fragment.graphql b/app/assets/javascripts/jobs/components/table/graphql/fragments/job.fragment.graphql
index 06b065a86ce..3038216fdfc 100644
--- a/app/assets/javascripts/jobs/components/table/graphql/fragments/job.fragment.graphql
+++ b/app/assets/javascripts/jobs/components/table/graphql/fragments/job.fragment.graphql
@@ -1,3 +1,7 @@
fragment Job on CiJob {
id
+ detailedStatus {
+ id
+ detailsPath
+ }
}
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
index 0a25dc5bea5..27e3b8028b7 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_tabs.vue
@@ -54,7 +54,9 @@ export default {
<gl-tab
v-for="tab in tabs"
:key="tab.text"
- :title-link-attributes="{ 'data-testid': tab.testId }"
+ :title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ 'data-testid': tab.testId,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@click="$emit('fetchJobsByStatus', tab.scope)"
>
<template #title>
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 9d255822250..a0f9db7409d 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -1,7 +1,7 @@
-import { isEmpty, isString } from 'lodash';
+import { isEmpty } from 'lodash';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
-export const headerTime = (state) => (state.job.started ? state.job.started : state.job.created_at);
+export const headerTime = (state) => state.job.started_at || state.job.created_at;
export const hasForwardDeploymentFailure = (state) =>
state?.job?.failure_reason === 'forward_deployment_failure';
@@ -13,10 +13,10 @@ export const shouldRenderCalloutMessage = (state) =>
!isEmpty(state.job.status) && !isEmpty(state.job.callout_message);
/**
- * When job has not started the key will be null
- * When job started the key will be a string with a date.
+ * When the job has not started the value of job.started_at will be null
+ * When job has started the value of job.started_at will be a string with a date.
*/
-export const shouldRenderTriggeredLabel = (state) => isString(state.job.started);
+export const shouldRenderTriggeredLabel = (state) => Boolean(state.job.started_at);
export const hasEnvironment = (state) => !isEmpty(state.job.deployment_status);
@@ -46,5 +46,5 @@ export const shouldRenderSharedRunnerLimitWarning = (state) =>
export const isScrollingDown = (state) => isScrolledToBottom() && !state.isJobLogComplete;
-export const hasRunnersForProject = (state) =>
+export const hasOfflineRunnersForProject = (state) =>
state?.job?.runners?.available && !state?.job?.runners?.online;
diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js
index 47568f0ecff..4959550e273 100644
--- a/app/assets/javascripts/lib/dompurify.js
+++ b/app/assets/javascripts/lib/dompurify.js
@@ -7,6 +7,7 @@ const defaultConfig = {
// Prevent possible XSS attacks with data-* attributes used by @rails/ujs
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1421
FORBID_ATTR: ['data-remote', 'data-url', 'data-type', 'data-method'],
+ FORBID_TAGS: ['style', 'mstyle'],
};
// Only icons urls from `gon` are allowed
diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js
index 07388f1fdfa..4e704eb69b2 100644
--- a/app/assets/javascripts/lib/gfm/index.js
+++ b/app/assets/javascripts/lib/gfm/index.js
@@ -32,7 +32,7 @@ const compilerFactory = (renderer) =>
* the MDast tree
*/
export const render = async ({ markdown, renderer }) => {
- const { value } = await createParser().use(compilerFactory(renderer)).process(markdown);
+ const { result } = await createParser().use(compilerFactory(renderer)).process(markdown);
- return value;
+ return result;
};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 96d019f62f2..1ed0cc3130b 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -4,15 +4,15 @@
import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
-import Cookies from 'js-cookie';
import { isFunction, defer } from 'lodash';
+import Cookies from '~/lib/utils/cookies';
import { SCOPED_LABEL_DELIMITER } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import { convertToCamelCase, convertToSnakeCase } from './text_utility';
import { isObject } from './type_utility';
import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => {
- const { page = '' } = document?.body?.dataset;
+ const { page = '' } = document.body.dataset;
return page.split(':')[index];
};
@@ -105,7 +105,7 @@ export const handleLocationHash = () => {
}
if (isInIssuePage()) {
- adjustment -= fixedIssuableTitle?.offsetHeight;
+ adjustment -= fixedIssuableTitle.offsetHeight;
}
if (isInMRPage()) {
@@ -157,7 +157,7 @@ export const contentTop = () => {
() => getOuterHeight('#js-peek'),
() => getOuterHeight('.navbar-gitlab'),
({ desktop }) => {
- const container = document.querySelector('.line-resolve-all-container');
+ const container = document.querySelector('.discussions-counter');
let size = 0;
if (!desktop && container) {
@@ -282,23 +282,51 @@ export const getSelectedFragment = (restrictToNode) => {
return documentFragment;
};
+function execInsertText(text) {
+ if (text === '') return document.execCommand('delete');
+
+ return document.execCommand('insertText', false, text);
+}
+
+/**
+ * This method inserts text into a textarea/input field.
+ * Uses `execCommand` if supported
+ *
+ * @param {HTMLElement} target - textarea/input to have text inserted into
+ * @param {String | function} text - text to be inserted
+ */
export const insertText = (target, text) => {
- // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
const { selectionStart, selectionEnd, value } = target;
-
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
-
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
- const newText = textBefore + insertedText + textAfter;
- // eslint-disable-next-line no-param-reassign
- target.value = newText;
- // eslint-disable-next-line no-param-reassign
- target.selectionStart = selectionStart + insertedText.length;
-
- // eslint-disable-next-line no-param-reassign
- target.selectionEnd = selectionStart + insertedText.length;
+ // The `execCommand` is officially deprecated. However, for `insertText`,
+ // there is currently no alternative. We need to use it in order to trigger
+ // the browser's undo tracking when we insert text.
+ // Per https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand on 2022-04-11,
+ // The Clipboard API can be used instead of execCommand in many cases,
+ // but execCommand is still sometimes useful. In particular, the Clipboard
+ // API doesn't replace the insertText command
+ // So we attempt to use it if possible. Otherwise, fall back to just replacing
+ // the value as before. In this case, Undo will be broken with inserted text.
+ // Testing on older versions of Firefox:
+ // 87 and below: does not work and falls through to just replacing value.
+ // 87 was released in Mar of 2021
+ // 89 and above: works well
+ // 89 was released in May of 2021
+ if (!execInsertText(insertedText)) {
+ const newText = textBefore + insertedText + textAfter;
+
+ // eslint-disable-next-line no-param-reassign
+ target.value = newText;
+
+ // eslint-disable-next-line no-param-reassign
+ target.selectionStart = selectionStart + insertedText.length;
+
+ // eslint-disable-next-line no-param-reassign
+ target.selectionEnd = selectionStart + insertedText.length;
+ }
// Trigger autosave
target.dispatchEvent(new Event('input'));
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
index 1d8eb73d3d7..3788d8ab20c 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue
@@ -3,7 +3,6 @@ import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
- cancelAction: { text: __('Cancel') },
directives: {
SafeHtml: GlSafeHtmlDirective,
},
@@ -36,6 +35,16 @@ export default {
required: false,
default: 'confirm',
},
+ cancelText: {
+ type: String,
+ required: false,
+ default: __('Cancel'),
+ },
+ cancelVariant: {
+ type: String,
+ required: false,
+ default: 'default',
+ },
modalHtmlMessage: {
type: String,
required: false,
@@ -71,7 +80,14 @@ export default {
};
},
cancelAction() {
- return this.hideCancel ? null : this.$options.cancelAction;
+ return this.hideCancel
+ ? null
+ : {
+ text: this.cancelText,
+ attributes: {
+ variant: this.cancelVariant,
+ },
+ };
},
shouldShowHeader() {
return Boolean(this.title?.length);
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
index 1adb6f9c26f..173116062c9 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
@@ -7,6 +7,8 @@ export function confirmAction(
primaryBtnText,
secondaryBtnVariant,
secondaryBtnText,
+ cancelBtnVariant,
+ cancelBtnText,
modalHtmlMessage,
title,
hideCancel,
@@ -28,6 +30,8 @@ export function confirmAction(
secondaryVariant: secondaryBtnVariant,
primaryVariant: primaryBtnVariant,
primaryText: primaryBtnText,
+ cancelVariant: cancelBtnVariant,
+ cancelText: cancelBtnText,
title,
modalHtmlMessage,
hideCancel,
diff --git a/app/assets/javascripts/lib/utils/cookies.js b/app/assets/javascripts/lib/utils/cookies.js
new file mode 100644
index 00000000000..be0491376c9
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/cookies.js
@@ -0,0 +1,8 @@
+import CookiesBuilder from 'js-cookie';
+
+// set default path for cookies
+const Cookies = CookiesBuilder.withAttributes({
+ path: gon.relative_url_root || '/',
+});
+
+export default Cookies;
diff --git a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
index 095a29a2eff..05f34db662a 100644
--- a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js
@@ -7,7 +7,7 @@ import { formatDate } from './date_format_utility';
*
* see https://github.com/hustcc/timeago.js/tree/v3.0.0/locales
*/
-const timeagoLanguageCode = languageCode().replace(/-/g, '_');
+export const timeagoLanguageCode = languageCode().replace(/-/g, '_');
/**
* Registers timeago locales
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index b52a736f153..4262329aae7 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -90,6 +90,20 @@ export const getParents = (element) => {
return parents;
};
+export const getParentByTagName = (element, tagName) => {
+ let parent = element.parentNode;
+
+ do {
+ if (parent.nodeName?.toLowerCase() === tagName?.toLowerCase()) {
+ return parent;
+ }
+
+ parent = parent.parentElement;
+ } while (parent);
+
+ return undefined;
+};
+
/**
* This method takes a HTML element and an object of attributes
* to save repeated calls to `setAttribute` when multiple
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 6b9be34235b..c5190592bb6 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -22,6 +22,7 @@ const httpStatusCodes = {
METHOD_NOT_ALLOWED: 405,
CONFLICT: 409,
GONE: 410,
+ PAYLOAD_TOO_LARGE: 413,
UNPROCESSABLE_ENTITY: 422,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500,
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 52fa90c7791..243de48948c 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -14,6 +14,8 @@ const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isUl>[*+-])|(?<isOl
// detect a horizontal rule that might be mistaken for a list item (not full pattern for an <hr>)
const HR_PATTERN = /^((\s{0,3}-+\s*-+\s*-+\s*[\s-]*)|(\s{0,3}\*+\s*\*+\s*\*+\s*[\s*]*))$/;
+let compositioningNoteText = false;
+
function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
@@ -363,10 +365,11 @@ function continueOlText(result, nextLineResult) {
}
function handleContinueList(e, textArea) {
- if (!gon.features?.markdownContinueLists) return;
if (!(e.key === 'Enter')) return;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (textArea.selectionStart !== textArea.selectionEnd) return;
+ // prevent unintended line breaks were inserted using Japanese IME on MacOS
+ if (compositioningNoteText) return;
const currentLine = lineBefore(textArea.value, textArea, false);
const result = currentLine.match(LIST_LINE_HEAD_PATTERN);
@@ -420,6 +423,14 @@ export function keypressNoteText(e) {
handleSurroundSelectedText(e, textArea);
}
+export function compositionStartNoteText() {
+ compositioningNoteText = true;
+}
+
+export function compositionEndNoteText() {
+ compositioningNoteText = false;
+}
+
export function updateTextForToolbarBtn($toolbarBtn) {
return updateText({
textArea: $toolbarBtn.closest('.md-area').find('textarea'),
@@ -435,6 +446,8 @@ export function updateTextForToolbarBtn($toolbarBtn) {
export function addMarkdownListeners(form) {
$('.markdown-area', form)
.on('keydown', keypressNoteText)
+ .on('compositionstart', compositionStartNoteText)
+ .on('compositionend', compositionEndNoteText)
.each(function attachTextareaShortcutHandlers() {
Shortcuts.initMarkdownEditorShortcuts($(this), updateTextForToolbarBtn);
});
@@ -474,6 +487,8 @@ export function addEditorMarkdownListeners(editor) {
export function removeMarkdownListeners(form) {
$('.markdown-area', form)
.off('keydown', keypressNoteText)
+ .off('compositionstart', compositionStartNoteText)
+ .off('compositionend', compositionEndNoteText)
.each(function removeTextareaShortcutHandlers() {
Shortcuts.removeMarkdownEditorShortcuts($(this));
});
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 419afa0a0a9..dad9cbcb6f6 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -6,6 +6,10 @@ import {
} from '~/lib/utils/constants';
import { allSingleQuotes } from '~/lib/utils/regexp';
+export const COLON = ':';
+export const HYPHEN = '-';
+export const NEWLINE = '\n';
+
/**
* Adds a , to a string composed by numbers, at every 3 chars.
*
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 335cd6a16e5..ff60fd2aecb 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -420,6 +420,19 @@ export function isSafeURL(url) {
}
/**
+ * Returns the sanitized url when not safe
+ *
+ * @param {String} url
+ * @returns {String}
+ */
+export function sanitizeUrl(url) {
+ if (!isSafeURL(url)) {
+ return 'about:blank';
+ }
+ return url;
+}
+
+/**
* Returns a normalized url
*
* https://gitlab.com/foo/../baz => https://gitlab.com/baz
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 54f69ef8e1b..bd000bb26fe 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -35,6 +35,17 @@ class UsersCache extends Cache {
// missing catch is intentional, error handling depends on use case
}
+ updateById(userId, data) {
+ if (!this.hasData(userId)) {
+ return;
+ }
+
+ this.internalStorage[userId] = {
+ ...this.internalStorage[userId],
+ ...data,
+ };
+ }
+
retrieveStatusById(userId) {
if (this.hasData(userId) && this.get(userId).status) {
return Promise.resolve(this.get(userId).status);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 8fc54be9c28..2f3cdc525a7 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,7 +1,6 @@
/* global $ */
import jQuery from 'jquery';
-import Cookies from 'js-cookie';
// bootstrap webpack, common libs, polyfills, and behaviors
import './webpack';
@@ -178,9 +177,6 @@ initUserTracking();
initLayoutNav();
initAlertHandler();
-// Set the default path for all cookies to GitLab's root directory
-Cookies.defaults.path = gon.relative_url_root || '/';
-
// `hashchange` is not triggered when link target is already in window.location
$body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
const href = this.getAttribute('href');
@@ -199,7 +195,11 @@ $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
* Quick fix: Get rid of jQuery for this implementation
*/
const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page);
-if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) {
+if (
+ !isBoardsPage &&
+ !window.gon?.features?.movedMrSidebar &&
+ (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')
+) {
const $rightSidebar = $('aside.right-sidebar');
const $layoutPage = $('.layout-page');
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index 0b97ce7e33e..14d628e455c 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -232,12 +232,10 @@ export default {
v-bind="tableAttrs.table"
class="members-table"
data-testid="members-table"
- head-variant="white"
stacked="lg"
:fields="filteredAndModifiedFields"
:items="members"
primary-key="id"
- thead-class="border-bottom"
:empty-text="__('No members found')"
show-empty
:tbody-tr-attr="tbodyTrAttr"
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 829e2264152..960b25bb552 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
import { getParameterValues, setUrlParams } from './lib/utils/url_utility';
@@ -31,8 +32,16 @@ function MergeRequest(opts) {
selector: '.detail-page-description',
lockVersion: this.$el.data('lockVersion'),
onSuccess: (result) => {
- document.querySelector('#task_status').innerText = result.task_status;
- document.querySelector('#task_status_short').innerText = result.task_status_short;
+ const taskStatus = document.querySelector('#task_status');
+ const taskStatusShort = document.querySelector('#task_status_short');
+
+ if (taskStatus) {
+ taskStatus.innerText = result.task_status;
+ }
+
+ if (taskStatusShort) {
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
},
onError: () => {
createFlash({
@@ -72,8 +81,7 @@ MergeRequest.prototype.initMRBtnListeners = function () {
const wipEvent = getParameterValues('merge_request[wip_event]', url)[0];
const mobileDropdown = draftToggle.closest('.dropdown.show');
- const loader = document.createElement('span');
- loader.classList.add('gl-spinner', 'gl-mr-3');
+ const loader = loadingIconForLegacyJS({ inline: true, classes: ['gl-mr-3'] });
if (mobileDropdown) {
$(mobileDropdown.firstElementChild).dropdown('toggle');
@@ -90,10 +98,13 @@ MergeRequest.prototype.initMRBtnListeners = function () {
MergeRequest.toggleDraftStatus(data.title, wipEvent === 'unwip');
})
.catch(() => {
- draftToggle.removeAttribute('disabled');
createFlash({
message: __('Something went wrong. Please try again.'),
});
+ })
+ .finally(() => {
+ draftToggle.removeAttribute('disabled');
+ loader.remove();
});
});
});
@@ -145,7 +156,11 @@ MergeRequest.toggleDraftStatus = function (title, isReady) {
} else {
toast(__('Marked as draft. Can only be merged when marked as ready.'));
}
- const titleEl = document.querySelector('.merge-request .detail-page-description .title');
+ const titleEl = document.querySelector(
+ `.merge-request .detail-page-${
+ window.gon?.features?.updatedMrHeader ? 'header' : 'description'
+ } .title`,
+ );
if (titleEl) {
titleEl.textContent = title;
@@ -162,7 +177,9 @@ MergeRequest.toggleDraftStatus = function (title, isReady) {
);
draftToggle.setAttribute('href', url);
- draftToggle.textContent = isReady ? __('Mark as draft') : __('Mark as ready');
+ draftToggle.querySelector('.gl-new-dropdown-item-text-wrapper').textContent = isReady
+ ? __('Mark as draft')
+ : __('Mark as ready');
});
}
};
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 61f7a079d77..e02109d1fd1 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,5 +1,4 @@
/* eslint-disable no-new, class-methods-use-this */
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
import Vue from 'vue';
import { getCookie, isMetaClick, parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
@@ -176,6 +175,8 @@ export default class MergeRequestTabs {
: null;
this.navbar = document.querySelector('.navbar-gitlab');
this.peek = document.getElementById('js-peek');
+ this.sidebar = document.querySelector('.js-right-sidebar');
+ this.pageLayout = document.querySelector('.layout-page');
this.paddingTop = 16;
this.scrollPositions = {};
@@ -282,7 +283,7 @@ export default class MergeRequestTabs {
if (action === 'commits') {
this.loadCommits(href);
- this.expandView();
+ // this.hideSidebar();
this.resetViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
} else if (action === 'new') {
@@ -301,13 +302,12 @@ export default class MergeRequestTabs {
*/
this.loadDiff(href);
}
- if (bp.getBreakpointSize() !== 'xl') {
- this.shrinkView();
- }
+ // this.hideSidebar();
this.expandViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
this.commitsTab.classList.remove('active');
} else if (action === 'pipelines') {
+ // this.hideSidebar();
this.resetViewContainer();
this.mountPipelinesView();
} else {
@@ -320,9 +320,7 @@ export default class MergeRequestTabs {
notesTab.classList.add('active');
}
- if (bp.getBreakpointSize() !== 'xs') {
- this.expandView();
- }
+ // this.showSidebar();
this.resetViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
}
@@ -509,19 +507,6 @@ export default class MergeRequestTabs {
}
}
- shrinkView() {
- const $gutterBtn = $('.js-sidebar-toggle:visible');
- const $expandSvg = $gutterBtn.find('.js-sidebar-expand');
-
- // Wait until listeners are set
- setTimeout(() => {
- // Only when sidebar is expanded
- if ($expandSvg.length && $expandSvg.hasClass('hidden')) {
- $gutterBtn.trigger('click', [true]);
- }
- }, 0);
- }
-
// Expand the issuable sidebar unless the user explicitly collapsed it
expandView() {
if (parseBoolean(getCookie('collapsed_gutter'))) {
@@ -538,4 +523,24 @@ export default class MergeRequestTabs {
}
}, 0);
}
+
+ hideSidebar() {
+ if (!isInVueNoteablePage() || this.cachedPageLayoutClasses) return;
+
+ this.cachedPageLayoutClasses = this.pageLayout.className;
+ this.pageLayout.classList.remove(
+ 'right-sidebar-collapsed',
+ 'right-sidebar-expanded',
+ 'page-with-icon-sidebar',
+ );
+ this.sidebar.style.width = '0px';
+ }
+
+ showSidebar() {
+ if (!isInVueNoteablePage() || !this.cachedPageLayoutClasses) return;
+
+ this.pageLayout.className = this.cachedPageLayoutClasses;
+ this.sidebar.style.width = '';
+ delete this.cachedPageLayoutClasses;
+ }
}
diff --git a/app/assets/javascripts/milestones/components/milestone_combobox.vue b/app/assets/javascripts/milestones/components/milestone_combobox.vue
index d7ffdfd7c5f..59d2a2b29b3 100644
--- a/app/assets/javascripts/milestones/components/milestone_combobox.vue
+++ b/app/assets/javascripts/milestones/components/milestone_combobox.vue
@@ -187,7 +187,7 @@ export default {
ref="searchBox"
v-model.trim="searchQuery"
class="gl-m-3"
- :placeholder="this.$options.translations.searchMilestones"
+ :placeholder="$options.translations.searchMilestones"
@input="onSearchBoxInput"
@keydown.enter.prevent="onSearchBoxEnter"
/>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index c4392dd3748..6a85833db27 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -492,7 +492,9 @@ export default {
v-if="!groupSingleEmptyState(groupData.key)"
:value="groupData.panels"
group="metrics-dashboard"
- :component-data="{ attrs: { class: 'row mx-0 w-100' } }"
+ :component-data="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ attrs: { class: 'row mx-0 w-100' },
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:disabled="!isRearrangingPanels"
@input="updatePanels(groupData.key, $event)"
>
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index 78e3b15913a..ff8ccded83b 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -19,6 +19,7 @@ import invalidUrl from '~/lib/utils/invalid_url';
import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
import { __, n__ } from '~/locale';
import TrackEventDirective from '~/vue_shared/directives/track_event';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { panelTypes } from '../constants';
import { graphDataToCsv } from '../csv_export';
@@ -57,6 +58,7 @@ export default {
GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
clipboardText: {
type: String,
@@ -141,6 +143,9 @@ export default {
return metrics.some(({ loading }) => loading);
},
logsPathWithTimeRange() {
+ if (!this.glFeatures.monitorLogging) {
+ return null;
+ }
const timeRange = this.zoomedTimeRange || this.timeRange;
if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index a1377415efe..7424c011052 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -31,6 +31,8 @@ export default function initMrNotes() {
const el = document.getElementById('js-vue-discussion-counter');
if (el) {
+ const { blocksMerge } = el.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el,
@@ -40,7 +42,11 @@ export default function initMrNotes() {
},
store,
render(createElement) {
- return createElement('discussion-counter');
+ return createElement('discussion-counter', {
+ props: {
+ blocksMerge: blocksMerge === 'true',
+ },
+ });
},
});
}
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index a9948fed3b6..4e03bed8737 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -6,7 +6,7 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import Autosave from '~/autosave';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import createFlash from '~/flash';
-import { statusBoxState } from '~/issuable/components/status_box.vue';
+import { badgeState } from '~/issuable/components/status_box.vue';
import httpStatusCodes from '~/lib/utils/http_status';
import {
capitalizeFirstCharacter,
@@ -90,9 +90,16 @@ export default {
return this.getUserData.id;
},
commentButtonTitle() {
- return this.noteType === constants.COMMENT
- ? this.$options.i18n.comment
- : this.$options.i18n.startThread;
+ const { comment, internalComment, startThread, startInternalThread } = this.$options.i18n;
+ if (this.noteIsConfidential) {
+ return this.noteType === constants.COMMENT ? internalComment : startInternalThread;
+ }
+ return this.noteType === constants.COMMENT ? comment : startThread;
+ },
+ textareaPlaceholder() {
+ return this.noteIsConfidential
+ ? this.$options.i18n.bodyPlaceholderInternal
+ : this.$options.i18n.bodyPlaceholder;
},
discussionsRequireResolution() {
return this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE;
@@ -266,7 +273,7 @@ export default {
const toggleState = this.isOpen ? this.closeIssuable : this.reopenIssuable;
toggleState()
- .then(() => statusBoxState.updateStatus && statusBoxState.updateStatus())
+ .then(() => badgeState.updateStatus && badgeState.updateStatus())
.then(refreshUserMergeRequestCounts)
.catch(() =>
createFlash({
@@ -371,7 +378,7 @@ export default {
data-testid="comment-field"
data-supports-quick-actions="true"
:aria-label="$options.i18n.comment"
- :placeholder="$options.i18n.bodyPlaceholder"
+ :placeholder="textareaPlaceholder"
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleEnter()"
@keydown.ctrl.enter="handleEnter()"
@@ -386,7 +393,7 @@ export default {
data-testid="add-to-review-button"
type="submit"
category="primary"
- variant="success"
+ variant="confirm"
@click.prevent="handleSaveDraft()"
>{{ __('Add to review') }}</gl-button
>
@@ -419,6 +426,7 @@ export default {
class="gl-mr-3"
:disabled="disableSubmitButton"
:tracking-label="trackingLabel"
+ :is-internal-note="noteIsConfidential"
:noteable-display-name="noteableDisplayName"
:discussions-require-resolution="discussionsRequireResolution"
@click="handleSave"
diff --git a/app/assets/javascripts/notes/components/comment_type_dropdown.vue b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
index 30ea5d3532e..543be838920 100644
--- a/app/assets/javascripts/notes/components/comment_type_dropdown.vue
+++ b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
@@ -32,6 +32,11 @@ export default {
required: false,
default: false,
},
+ isInternalNote: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
noteableDisplayName: {
type: String,
required: true,
@@ -48,18 +53,43 @@ export default {
isNoteTypeDiscussion() {
return this.noteType === constants.DISCUSSION;
},
+ dropdownCommentButtonTitle() {
+ const { comment, internalComment } = this.$options.i18n.submitButton;
+
+ return this.isInternalNote ? internalComment : comment;
+ },
+ dropdownStartThreadButtonTitle() {
+ const { startThread, startInternalThread } = this.$options.i18n.submitButton;
+
+ return this.isInternalNote ? startInternalThread : startThread;
+ },
commentButtonTitle() {
- return this.noteType === constants.COMMENT
- ? this.$options.i18n.comment
- : this.$options.i18n.startThread;
+ const { comment, internalComment, startThread, startInternalThread } = this.$options.i18n;
+
+ if (this.isInternalNote) {
+ return this.noteType === constants.COMMENT ? internalComment : startInternalThread;
+ }
+ return this.noteType === constants.COMMENT ? comment : startThread;
},
startDiscussionDescription() {
- return this.discussionsRequireResolution
- ? this.$options.i18n.discussionThatNeedsResolution
- : this.$options.i18n.discussion;
+ const {
+ discussionThatNeedsResolution,
+ internalDiscussionThatNeedsResolution,
+ discussion,
+ internalDiscussion,
+ } = this.$options.i18n;
+
+ if (this.isInternalNote) {
+ return this.discussionsRequireResolution
+ ? internalDiscussionThatNeedsResolution
+ : internalDiscussion;
+ }
+ return this.discussionsRequireResolution ? discussionThatNeedsResolution : discussion;
},
commentDescription() {
- return sprintf(this.$options.i18n.submitButton.commentHelp, {
+ const { commentHelp, internalCommentHelp } = this.$options.i18n.submitButton;
+
+ return sprintf(this.isInternalNote ? internalCommentHelp : commentHelp, {
noteableDisplayName: this.noteableDisplayName,
});
},
@@ -101,7 +131,7 @@ export default {
:is-checked="isNoteTypeComment"
@click.stop.prevent="setNoteTypeToComment"
>
- <strong>{{ $options.i18n.submitButton.comment }}</strong>
+ <strong>{{ dropdownCommentButtonTitle }}</strong>
<p class="gl-m-0">{{ commentDescription }}</p>
</gl-dropdown-item>
<gl-dropdown-divider />
@@ -111,7 +141,7 @@ export default {
data-qa-selector="discussion_menu_item"
@click.stop.prevent="setNoteTypeToDiscussion"
>
- <strong>{{ $options.i18n.submitButton.startThread }}</strong>
+ <strong>{{ dropdownStartThreadButtonTitle }}</strong>
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
</gl-dropdown-item>
</gl-dropdown>
diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue
index 45d97f278dc..5210d2ca287 100644
--- a/app/assets/javascripts/notes/components/diff_discussion_header.vue
+++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue
@@ -98,13 +98,14 @@ export default {
<template>
<div class="discussion-header note-wrapper">
- <div v-once class="timeline-icon align-self-start flex-shrink-0">
+ <div v-once class="timeline-icon gl-align-self-start gl-flex-shrink-0 gl-flex-shrink gl-mr-4">
<user-avatar-link
v-if="author"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
- :img-size="40"
+ :img-size="32"
+ :img-css-classes="'gl-mr-0!' /* NOTE: this is needed only while we migrate user-avatar-image to GlAvatar (https://gitlab.com/groups/gitlab-org/-/epics/7731) */"
/>
</div>
<div class="timeline-content w-100">
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 33819c78c0f..f746f7ed0ed 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlIcon, GlButton, GlButtonGroup } from '@gitlab/ui';
+import { GlTooltipDirective, GlButton, GlButtonGroup } from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
import { __ } from '~/locale';
import discussionNavigation from '../mixins/discussion_navigation';
@@ -9,46 +9,41 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
- GlIcon,
GlButton,
GlButtonGroup,
},
mixins: [discussionNavigation],
+ props: {
+ blocksMerge: {
+ type: Boolean,
+ required: true,
+ },
+ },
computed: {
...mapGetters([
- 'getUserData',
'getNoteableData',
'resolvableDiscussionsCount',
'unresolvedDiscussionsCount',
- 'discussions',
+ 'allResolvableDiscussions',
]),
- isLoggedIn() {
- return this.getUserData.id;
- },
allResolved() {
return this.unresolvedDiscussionsCount === 0;
},
- resolveAllDiscussionsIssuePath() {
- return this.getNoteableData.create_issue_to_resolve_discussions_path;
- },
- toggeableDiscussions() {
- return this.discussions.filter((discussion) => !discussion.individual_note);
- },
allExpanded() {
- return this.toggeableDiscussions.every((discussion) => discussion.expanded);
- },
- lineResolveClass() {
- return this.allResolved ? 'line-resolve-btn is-active' : 'line-resolve-text';
+ return this.allResolvableDiscussions.every((discussion) => discussion.expanded);
},
toggleThreadsLabel() {
return this.allExpanded ? __('Collapse all threads') : __('Expand all threads');
},
+ resolveAllDiscussionsIssuePath() {
+ return this.getNoteableData.create_issue_to_resolve_discussions_path;
+ },
},
methods: {
...mapActions(['setExpandDiscussions']),
handleExpandDiscussions() {
this.setExpandDiscussions({
- discussionIds: this.toggeableDiscussions.map((discussion) => discussion.id),
+ discussionIds: this.allResolvableDiscussions.map((discussion) => discussion.id),
expanded: !this.allExpanded,
});
},
@@ -60,21 +55,61 @@ export default {
<div
v-if="resolvableDiscussionsCount > 0"
ref="discussionCounter"
- class="line-resolve-all-container full-width-mobile gl-display-flex d-sm-flex"
+ class="gl-display-flex discussions-counter"
>
- <div class="line-resolve-all">
- <span :class="lineResolveClass">
- <template v-if="allResolved">
- <gl-icon name="check-circle-filled" />
- {{ __('All threads resolved') }}
- </template>
- <template v-else>
- {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
- </template>
- </span>
+ <div
+ class="gl-display-flex gl-align-items-center gl-pl-4 gl-rounded-base gl-mr-3"
+ :class="{
+ 'gl-bg-orange-50': blocksMerge && !allResolved,
+ 'gl-bg-gray-50': !blocksMerge || allResolved,
+ 'gl-pr-4': allResolved,
+ 'gl-pr-2': !allResolved,
+ }"
+ data-testid="discussions-counter-text"
+ >
+ <template v-if="allResolved">
+ {{ __('All threads resolved!') }}
+ </template>
+ <template v-else>
+ {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
+ <gl-button-group class="gl-ml-3">
+ <gl-button
+ v-gl-tooltip.hover
+ :title="__('Jump to previous unresolved thread')"
+ :aria-label="__('Jump to previous unresolved thread')"
+ class="discussion-previous-btn gl-rounded-base! gl-px-2!"
+ data-track-action="click_button"
+ data-track-label="mr_previous_unresolved_thread"
+ data-track-property="click_previous_unresolved_thread_top"
+ icon="angle-up"
+ category="tertiary"
+ @click="jumpToPreviousDiscussion"
+ />
+ <gl-button
+ v-gl-tooltip.hover
+ :title="__('Jump to next unresolved thread')"
+ :aria-label="__('Jump to next unresolved thread')"
+ class="discussion-next-btn gl-rounded-base! gl-px-2!"
+ data-track-action="click_button"
+ data-track-label="mr_next_unresolved_thread"
+ data-track-property="click_next_unresolved_thread_top"
+ icon="angle-down"
+ category="tertiary"
+ @click="jumpToNextDiscussion"
+ />
+ </gl-button-group>
+ </template>
</div>
<gl-button-group>
<gl-button
+ v-gl-tooltip
+ :title="toggleThreadsLabel"
+ :aria-label="toggleThreadsLabel"
+ class="toggle-all-discussions-btn"
+ :icon="allExpanded ? 'collapse' : 'expand'"
+ @click="handleExpandDiscussions"
+ />
+ <gl-button
v-if="resolveAllDiscussionsIssuePath && !allResolved"
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
@@ -83,26 +118,6 @@ export default {
class="new-issue-for-discussion discussion-create-issue-btn"
icon="issue-new"
/>
- <gl-button
- v-if="isLoggedIn && !allResolved"
- v-gl-tooltip
- :title="__('Jump to next unresolved thread')"
- :aria-label="__('Jump to next unresolved thread')"
- class="discussion-next-btn"
- data-track-action="click_button"
- data-track-label="mr_next_unresolved_thread"
- data-track-property="click_next_unresolved_thread_top"
- icon="comment-next"
- @click="jumpToNextDiscussion"
- />
- <gl-button
- v-gl-tooltip
- :title="toggleThreadsLabel"
- :aria-label="toggleThreadsLabel"
- class="toggle-all-discussions-btn"
- :icon="allExpanded ? 'angle-up' : 'angle-down'"
- @click="handleExpandDiscussions"
- />
</gl-button-group>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index fe17a061c0a..6c9bc4461c2 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -4,6 +4,7 @@ import { GlSafeHtmlDirective } from '@gitlab/ui';
import { escape } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex';
+import { __ } from '~/locale';
import '~/behaviors/markdown/render_gfm';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
import autosave from '../mixins/autosave';
@@ -69,6 +70,9 @@ export default {
noteBody() {
return this.note.note;
},
+ saveButtonTitle() {
+ return this.note.confidential ? __('Save internal note') : __('Save comment');
+ },
hasSuggestion() {
return this.note.suggestions && this.note.suggestions.length;
},
@@ -180,6 +184,7 @@ export default {
:note-id="note.id"
:line="line"
:note="note"
+ :save-button-title="saveButtonTitle"
:help-page-path="helpPagePath"
:discussion="discussion"
:resolve-discussion="note.resolve_discussion"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index c1e763d81ee..5dd032abd72 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -8,9 +8,11 @@ import markdownField from '~/vue_shared/components/markdown/field.vue';
import eventHub from '../event_hub';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
+import { COMMENT_FORM } from '../i18n';
import CommentFieldLayout from './comment_field_layout.vue';
export default {
+ i18n: COMMENT_FORM,
name: 'NoteForm',
components: {
markdownField,
@@ -133,6 +135,11 @@ export default {
.some((n) => n.current_user?.can_resolve_discussion) || this.isDraft
);
},
+ textareaPlaceholder() {
+ return this.discussionNote?.confidential
+ ? this.$options.i18n.bodyPlaceholderInternal
+ : this.$options.i18n.bodyPlaceholder;
+ },
noteHash() {
if (this.noteId) {
return `#note_${this.noteId}`;
@@ -350,7 +357,7 @@ export default {
data-qa-selector="reply_field"
dir="auto"
:aria-label="__('Reply to comment')"
- :placeholder="__('Write a comment or drag your files here…')"
+ :placeholder="textareaPlaceholder"
@keydown.meta.enter="handleKeySubmit()"
@keydown.ctrl.enter="handleKeySubmit()"
@keydown.exact.up="editMyLastNote()"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 11b427b9346..1ad9d593ccc 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,6 +1,7 @@
<script>
import {
GlIcon,
+ GlBadge,
GlLoadingIcon,
GlTooltipDirective,
GlSafeHtmlDirective as SafeHtml,
@@ -10,8 +11,6 @@ import { __, s__ } from '~/locale';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
-import { NOTEABLE_TYPE_MAPPING } from '../constants';
-
export default {
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
components: {
@@ -19,6 +18,7 @@ export default {
GitlabTeamMemberBadge: () =>
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
GlIcon,
+ GlBadge,
GlLoadingIcon,
UserNameWithStatus,
},
@@ -111,13 +111,7 @@ export default {
return this.author.name;
},
noteConfidentialityTooltip() {
- if (
- this.noteableType === NOTEABLE_TYPE_MAPPING.Issue ||
- this.noteableType === NOTEABLE_TYPE_MAPPING.MergeRequest
- ) {
- return s__('Notes|This comment is confidential and only visible to project members');
- }
- return s__('Notes|This comment is confidential and only visible to group members');
+ return s__('Notes|This internal note will always remain confidential');
},
},
mounted() {
@@ -236,15 +230,16 @@ export default {
</a>
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</template>
- <gl-icon
+ <gl-badge
v-if="isConfidential"
v-gl-tooltip:tooltipcontainer.bottom
- data-testid="confidentialIndicator"
- name="eye-slash"
- :size="16"
+ data-testid="internalNoteIndicator"
+ variant="warning"
+ size="sm"
:title="noteConfidentialityTooltip"
- class="gl-ml-1 gl-text-orange-700 align-middle"
- />
+ >
+ {{ __('Internal note') }}
+ </gl-badge>
<slot name="extra-controls"></slot>
<gl-loading-icon
v-if="showSpinner"
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 000eb3bdff3..0f5a517a4c5 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -7,7 +7,7 @@ import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending';
-import { s__, __ } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -95,6 +95,9 @@ export default {
isLoggedIn() {
return isLoggedIn();
},
+ commentType() {
+ return this.discussion.confidential ? __('internal note') : __('comment');
+ },
autosaveKey() {
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
},
@@ -104,6 +107,9 @@ export default {
firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
+ saveButtonTitle() {
+ return this.discussion.confidential ? __('Reply internally') : __('Comment');
+ },
shouldShowJumpToNextDiscussion() {
return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
},
@@ -174,9 +180,15 @@ export default {
},
cancelReplyForm: ignoreWhilePending(async function cancelReplyForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
- const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
+ const msg = sprintf(
+ s__('Notes|Are you sure you want to cancel creating this %{commentType}?'),
+ { commentType: this.commentType },
+ );
- const confirmed = await confirmAction(msg);
+ const confirmed = await confirmAction(msg, {
+ primaryBtnText: __('Discard changes'),
+ cancelBtnText: __('Continue editing'),
+ });
if (!confirmed) {
return;
@@ -308,7 +320,7 @@ export default {
ref="noteForm"
:discussion="discussion"
:line="diffLine"
- save-button-title="Comment"
+ :save-button-title="saveButtonTitle"
:autosave-key="autosaveKey"
@handleFormUpdateAddToReview="addReplyToReview"
@handleFormUpdate="saveReply"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index a2fbb242222..cda22b58c5b 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -108,6 +108,9 @@ export default {
author() {
return this.note.author;
},
+ commentType() {
+ return this.note.confidential ? __('internal note') : __('comment');
+ },
classNameBindings() {
return {
[`note-row-${this.note.id}`]: true,
@@ -246,14 +249,19 @@ export default {
this.$emit('handleEdit');
},
async deleteHandler() {
- const typeOfComment = this.note.isDraft ? __('pending comment') : __('comment');
+ let { commentType } = this;
+
+ if (this.note.isDraft) {
+ // Draft internal notes (i.e. MR review comments) are not supported.
+ commentType = __('pending comment');
+ }
- const msg = sprintf(__('Are you sure you want to delete this %{typeOfComment}?'), {
- typeOfComment,
+ const msg = sprintf(__('Are you sure you want to delete this %{commentType}?'), {
+ commentType,
});
const confirmed = await confirmAction(msg, {
primaryBtnVariant: 'danger',
- primaryBtnText: __('Delete Comment'),
+ primaryBtnText: this.note.confidential ? __('Delete Internal Note') : __('Delete Comment'),
});
if (confirmed) {
@@ -356,7 +364,9 @@ export default {
isDirty,
}) {
if (shouldConfirm && isDirty) {
- const msg = __('Are you sure you want to cancel editing this comment?');
+ const msg = sprintf(__('Are you sure you want to cancel editing this %{commentType}?'), {
+ commentType: this.commentType,
+ });
const confirmed = await confirmAction(msg, {
primaryBtnText: __('Cancel editing'),
primaryBtnVariant: 'danger',
diff --git a/app/assets/javascripts/notes/i18n.js b/app/assets/javascripts/notes/i18n.js
index 951fa9733d4..4c0ee81bec0 100644
--- a/app/assets/javascripts/notes/i18n.js
+++ b/app/assets/javascripts/notes/i18n.js
@@ -6,19 +6,26 @@ export const COMMENT_FORM = {
),
note: __('Note'),
comment: __('Comment'),
+ internalComment: __('Add internal note'),
issue: __('issue'),
startThread: __('Start thread'),
+ startInternalThread: __('Start internal thread'),
mergeRequest: __('merge request'),
epic: __('epic'),
bodyPlaceholder: __('Write a comment or drag your files here…'),
- confidential: s__('Notes|Make this comment confidential'),
+ bodyPlaceholderInternal: __('Write an internal note or drag your files here…'),
+ confidential: s__('Notes|Make this an internal note'),
confidentialVisibility: s__(
- 'Notes|Confidential comments are only visible to members with the role of Reporter or higher',
+ 'Notes|Internal notes are only visible to the author, assignees, and members with the role of Reporter or higher',
),
discussionThatNeedsResolution: __(
'Discuss a specific suggestion or question that needs to be resolved.',
),
+ internalDiscussionThatNeedsResolution: __(
+ 'Discuss a specific suggestion or question internally that needs to be resolved.',
+ ),
discussion: __('Discuss a specific suggestion or question.'),
+ internalDiscussion: __('Discuss a specific suggestion or question internally.'),
actionButtonWithNote: __('%{actionText} & %{openOrClose} %{noteable}'),
actionButton: {
withNote: {
@@ -32,7 +39,10 @@ export const COMMENT_FORM = {
},
submitButton: {
startThread: __('Start thread'),
+ startInternalThread: __('Start internal thread'),
comment: __('Comment'),
+ internalComment: __('Add internal note'),
commentHelp: __('Add a general comment to this %{noteableDisplayName}.'),
+ internalCommentHelp: __('Add a confidential internal note to this %{noteableDisplayName}.'),
},
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 204e704e504..0cfc17a6ae9 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -17,6 +17,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import sidebarTimeTrackingEventHub from '~/sidebar/event_hub';
import TaskList from '~/task_list';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
+import SidebarStore from '~/sidebar/stores/sidebar_store';
import * as constants from '../constants';
import eventHub from '../event_hub';
import * as types from './mutation_types';
@@ -369,7 +370,14 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
}
const processQuickActions = (res) => {
- const { errors: { commands_only: message } = { commands_only: null } } = res;
+ const {
+ errors: { commands_only: commandsOnly, command_names: commandNames } = {
+ commands_only: null,
+ command_names: [],
+ },
+ } = res;
+ let message = commandsOnly;
+
/*
The following reply means that quick actions have been successfully applied:
@@ -387,6 +395,13 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
confidentialWidget.setConfidentiality();
}
+ const commands = ['approve', 'merge', 'assign_reviewer', 'assign'];
+ const commandUpdatesAttentionRequest = commandNames[0].some((c) => commands.includes(c));
+
+ if (commandUpdatesAttentionRequest && SidebarStore.singleton.currentUserHasAttention) {
+ message = sprintf(__('%{message}. Your attention request was removed.'), { message });
+ }
+
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
createFlash({
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index a710ac0ccf5..1fe82d96435 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -1,5 +1,6 @@
import { flattenDeep, clone } from 'lodash';
-import { statusBoxState } from '~/issuable/components/status_box.vue';
+import { match } from '~/diffs/utils/diff_file';
+import { badgeState } from '~/issuable/components/status_box.vue';
import { isInMRPage } from '~/lib/utils/common_utils';
import * as constants from '../constants';
import { collapseSystemNotes } from './collapse_utils';
@@ -84,8 +85,7 @@ export const getBlockedByIssues = (state) => state.noteableData.blocked_by_issue
export const userCanReply = (state) => Boolean(state.noteableData.current_user.can_create_note);
-export const openState = (state) =>
- isInMRPage() ? statusBoxState.state : state.noteableData.state;
+export const openState = (state) => (isInMRPage() ? badgeState.state : state.noteableData.state);
export const getUserData = (state) => state.userData || {};
@@ -179,29 +179,42 @@ export const unresolvedDiscussionsIdsByDate = (state, getters) =>
// Sorts the array of resolvable yet unresolved discussions by
// comparing file names first. If file names are the same, compares
// line numbers.
-export const unresolvedDiscussionsIdsByDiff = (state, getters) =>
- getters.allResolvableDiscussions
+export const unresolvedDiscussionsIdsByDiff = (state, getters, allState) => {
+ const authoritativeFiles = allState.diffs.diffFiles;
+
+ return getters.allResolvableDiscussions
.filter((d) => !d.resolved && d.active)
.sort((a, b) => {
+ let order = 0;
+
if (!a.diff_file || !b.diff_file) {
- return 0;
+ return order;
}
- // Get file names comparison result
- const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path);
+ const authoritativeA = authoritativeFiles.find((source) =>
+ match({ fileA: source, fileB: a.diff_file, mode: 'mr' }),
+ );
+ const authoritativeB = authoritativeFiles.find((source) =>
+ match({ fileA: source, fileB: b.diff_file, mode: 'mr' }),
+ );
+
+ if (authoritativeA && authoritativeB) {
+ order = authoritativeA.order - authoritativeB.order;
+ }
// Get the line numbers, to compare within the same file
const aLines = [a.position.new_line, a.position.old_line];
const bLines = [b.position.new_line, b.position.old_line];
- return filenameComparison < 0 ||
- (filenameComparison === 0 &&
+ return order < 0 ||
+ (order === 0 &&
// .max() because one of them might be zero (if removed/added)
Math.max(aLines[0], aLines[1]) < Math.max(bLines[0], bLines[1]))
? -1
: 1;
})
.map((d) => d.id);
+};
export const resolvedDiscussionCount = (state, getters) => {
const resolvedMap = getters.resolvedDiscussionsById;
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
index 7a8a1bbcf09..2da8ca2d8a8 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
@@ -83,11 +83,13 @@ export default {
modal-id="delete-tag-modal"
ok-variant="danger"
size="sm"
- :action-primary="{
+ :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: __('Delete'),
attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }],
- }"
- :action-cancel="{ text: __('Cancel') }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: __('Cancel'),
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@primary="$emit('confirmDelete')"
@cancel="$emit('cancelDelete')"
@change="projectPath = ''"
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
index 7659ba5f9ea..9e8eb92d87a 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
@@ -168,7 +168,9 @@ export default {
<div>
<persisted-search
class="gl-mb-5"
- :sortable-fields="[$options.searchConfig.NAME_SORT_FIELD]"
+ :sortable-fields="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ $options.searchConfig.NAME_SORT_FIELD,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy"
default-sort="asc"
@update="handleSearchUpdate"
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
index 1f52e319ad0..3ae69731537 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue
@@ -1,7 +1,9 @@
<script>
-import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { GlIcon, GlPopover, GlLink, GlSprintf } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { timeTilRun } from '../../utils';
import {
- CLEANUP_TIMED_OUT_ERROR_MESSAGE,
+ PARTIAL_CLEANUP_CONTINUE_MESSAGE,
CLEANUP_STATUS_SCHEDULED,
CLEANUP_STATUS_ONGOING,
CLEANUP_STATUS_UNFINISHED,
@@ -15,9 +17,9 @@ export default {
name: 'CleanupStatus',
components: {
GlIcon,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
+ GlPopover,
+ GlLink,
+ GlSprintf,
},
props: {
status: {
@@ -29,12 +31,17 @@ export default {
);
},
},
+ expirationPolicy: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
},
i18n: {
CLEANUP_STATUS_SCHEDULED,
CLEANUP_STATUS_ONGOING,
CLEANUP_STATUS_UNFINISHED,
- CLEANUP_TIMED_OUT_ERROR_MESSAGE,
+ PARTIAL_CLEANUP_CONTINUE_MESSAGE,
},
computed: {
showStatus() {
@@ -46,26 +53,57 @@ export default {
statusText() {
return this.$options.i18n[`CLEANUP_STATUS_${this.status}`];
},
- expireIconClass() {
- return this.failedDelete ? 'gl-text-orange-500' : '';
+ calculatedTimeTilNextRun() {
+ return timeTilRun(this.expirationPolicy?.next_run);
},
},
+ statusPopoverOptions: {
+ triggers: 'hover',
+ placement: 'top',
+ },
+ cleanupPolicyHelpPage: helpPagePath(
+ 'user/packages/container_registry/reduce_container_registry_storage.html',
+ { anchor: 'how-the-cleanup-policy-works' },
+ ),
};
</script>
<template>
- <div v-if="showStatus" class="gl-display-inline-flex gl-align-items-center">
- <gl-icon name="expire" data-testid="main-icon" :class="expireIconClass" />
+ <div
+ v-if="showStatus"
+ id="status-popover-container"
+ class="gl-display-inline-flex gl-align-items-center"
+ >
+ <div class="gl-display-inline-flex gl-align-items-center">
+ <gl-icon name="expire" data-testid="main-icon" />
+ </div>
<span class="gl-mx-2">
{{ statusText }}
</span>
<gl-icon
v-if="failedDelete"
- v-gl-tooltip="{ title: $options.i18n.CLEANUP_TIMED_OUT_ERROR_MESSAGE }"
+ id="status-info"
:size="14"
- class="gl-text-black-normal"
+ class="gl-text-gray-500"
data-testid="extra-info"
- name="information"
+ name="information-o"
/>
+ <gl-popover
+ target="status-info"
+ container="status-popover-container"
+ v-bind="$options.statusPopoverOptions"
+ >
+ <template #title>
+ {{ $options.i18n.CLEANUP_STATUS_UNFINISHED }}
+ </template>
+ <gl-sprintf :message="$options.i18n.PARTIAL_CLEANUP_CONTINUE_MESSAGE">
+ <template #time>{{ calculatedTimeTilNextRun }}</template
+ ><template #link="{ content }"
+ ><gl-link :href="$options.cleanupPolicyHelpPage" class="gl-font-sm" target="_blank">{{
+ content
+ }}</gl-link></template
+ >
+ </gl-sprintf>
+ </gl-popover>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue
index 5bd13322ebb..6f1f67e251f 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue
@@ -22,6 +22,11 @@ export default {
type: Object,
required: true,
},
+ expirationPolicy: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
},
computed: {
showPagination() {
@@ -38,6 +43,7 @@ export default {
:key="index"
:item="listItem"
:metadata-loading="metadataLoading"
+ :expiration-policy="expirationPolicy"
@delete="$emit('delete', $event)"
/>
<div class="gl-display-flex gl-justify-content-center">
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index 484903354e8..d76a8245b63 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -6,12 +6,10 @@ import { n__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
- ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
LIST_DELETE_BUTTON_DISABLED,
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
- CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
IMAGE_MIGRATING_STATE,
@@ -45,6 +43,11 @@ export default {
default: false,
required: false,
},
+ expirationPolicy: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
},
i18n: {
REMOVE_REPOSITORY_LABEL,
@@ -73,15 +76,6 @@ export default {
this.item.tagsCount,
);
},
- warningIconText() {
- if (this.failedDelete) {
- return ASYNC_DELETE_IMAGE_ERROR_MESSAGE;
- }
- if (this.item.expirationPolicyStartedAt) {
- return CLEANUP_TIMED_OUT_ERROR_MESSAGE;
- }
- return null;
- },
imageName() {
return this.item.name ? this.item.path : `${this.item.path}/ ${ROOT_IMAGE_TEXT}`;
},
@@ -140,6 +134,7 @@ export default {
v-if="item.expirationPolicyCleanupStatus"
class="ml-2"
:status="item.expirationPolicyCleanupStatus"
+ :expiration-policy="expirationPolicy"
/>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
index 154e176dc6e..4ffd8390e4d 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
@@ -7,7 +7,6 @@ import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import {
CONTAINER_REGISTRY_TITLE,
- LIST_INTRO_TEXT,
EXPIRATION_POLICY_WILL_RUN_IN,
EXPIRATION_POLICY_DISABLED_TEXT,
SET_UP_CLEANUP,
@@ -87,19 +86,12 @@ export default {
? sprintf(EXPIRATION_POLICY_WILL_RUN_IN, { time: this.timeTillRun })
: EXPIRATION_POLICY_DISABLED_TEXT;
},
- infoMessages() {
- return [{ text: LIST_INTRO_TEXT, link: this.helpPagePath }];
- },
},
};
</script>
<template>
- <title-area
- :title="$options.i18n.CONTAINER_REGISTRY_TITLE"
- :info-messages="infoMessages"
- :metadata-loading="metadataLoading"
- >
+ <title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE" :metadata-loading="metadataLoading">
<template #right-actions>
<slot name="commands"></slot>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
index 3c7f7ca9aa8..2a58933cd64 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
@@ -87,7 +87,7 @@ export const CLEANUP_DISABLED_TOOLTIP = s__(
export const CLEANUP_STATUS_SCHEDULED = s__('ContainerRegistry|Cleanup will run soon');
export const CLEANUP_STATUS_ONGOING = s__('ContainerRegistry|Cleanup is ongoing');
-export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Cleanup timed out');
+export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Partial cleanup complete');
export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while scheduling the image for deletion.',
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
index e584da23edb..9d0ecfd2dcb 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
@@ -10,7 +10,7 @@ export const DELETE_ALERT_TITLE = s__('ContainerRegistry|Some tags were not dele
export const DELETE_ALERT_LINK_TEXT = s__(
'ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}',
);
-export const CLEANUP_TIMED_OUT_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Cleanup timed out before it could delete all tags',
+export const PARTIAL_CLEANUP_CONTINUE_MESSAGE = s__(
+ 'ContainerRegistry|The cleanup will continue within %{time}. %{linkStart}Learn more%{linkEnd}',
);
export const SET_UP_CLEANUP = s__('ContainerRegistry|Set up cleanup');
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
index c7022d6070f..ceaf8a65a10 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
@@ -8,9 +8,6 @@ export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection e
export const CONNECTION_ERROR_MESSAGE = s__(
`ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`,
);
-export const LIST_INTRO_TEXT = s__(
- `ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,
-);
export const LIST_DELETE_BUTTON_DISABLED = s__(
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
);
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
index 916740f41b8..e2036d9e63d 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
@@ -1,4 +1,4 @@
-query getContainerRepositoryDetails($id: ID!) {
+query getContainerRepositoryDetails($id: ContainerRepositoryID!) {
containerRepository(id: $id) {
id
name
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql
index f1f67b98407..1faa9dec795 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql
@@ -1,4 +1,4 @@
-query getContainerRepositoryMetadata($id: ID!) {
+query getContainerRepositoryMetadata($id: ContainerRepositoryID!) {
containerRepository(id: $id) {
id
tagsCount
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
index 8c577cc7b17..e57ac2a9efe 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
@@ -1,7 +1,7 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getContainerRepositoryTags(
- $id: ID!
+ $id: ContainerRepositoryID!
$first: Int
$last: Int
$after: String
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
index d1cab406984..c1bd71de646 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
@@ -336,6 +336,7 @@ export default {
:images="images"
:metadata-loading="$apollo.queries.additionalDetails.loading"
:page-info="pageInfo"
+ :expiration-policy="config.expirationPolicy"
@delete="deleteImage"
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
@@ -370,7 +371,10 @@ export default {
ref="deleteModal"
size="sm"
modal-id="delete-image-modal"
- :action-primary="{ text: __('Remove'), attributes: { variant: 'danger' } }"
+ :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: __('Remove'),
+ attributes: { variant: 'danger' },
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@primary="doDelete"
@cancel="track('cancel_delete')"
>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js
new file mode 100644
index 00000000000..ffdaf9f2f17
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js
@@ -0,0 +1,8 @@
+import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
+
+export const timeTilRun = (time) => {
+ if (!time) return '';
+
+ const difference = calculateRemainingMilliseconds(time);
+ return approximateDuration(difference / 1000);
+};
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index 67c2ca02d20..1faff1ff4de 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -16,10 +16,7 @@ import Api from '~/api';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
-import {
- DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
- DEPENDENCY_PROXY_DOCS_PATH,
-} from '~/packages_and_registries/settings/group/constants';
+import { DEPENDENCY_PROXY_DOCS_PATH } from '~/packages_and_registries/settings/group/constants';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
@@ -42,11 +39,8 @@ export default {
directives: {
GlModalDirective,
},
- inject: ['groupPath', 'groupId', 'dependencyProxyAvailable', 'noManifestsIllustration'],
+ inject: ['groupPath', 'groupId', 'noManifestsIllustration'],
i18n: {
- proxyNotAvailableText: s__(
- 'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
- ),
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
@@ -80,32 +74,20 @@ export default {
apollo: {
group: {
query: getDependencyProxyDetailsQuery,
- skip() {
- return !this.dependencyProxyAvailable;
- },
variables() {
return this.queryVariables;
},
},
},
computed: {
- infoMessages() {
- return [
- {
- text: DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
- link: DEPENDENCY_PROXY_DOCS_PATH,
- },
- ];
- },
-
queryVariables() {
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
},
pageInfo() {
- return this.group.dependencyProxyManifests.pageInfo;
+ return this.group.dependencyProxyManifests?.pageInfo;
},
manifests() {
- return this.group.dependencyProxyManifests.nodes;
+ return this.group.dependencyProxyManifests?.nodes;
},
modalTitleWithCount() {
return sprintf(
@@ -132,7 +114,10 @@ export default {
);
},
showDeleteDropdown() {
- return this.group.dependencyProxyBlobCount > 0;
+ return this.group.dependencyProxyManifests?.nodes.length > 0;
+ },
+ showDependencyProxyImagePrefix() {
+ return this.group.dependencyProxyImagePrefix?.length > 0;
},
},
methods: {
@@ -181,7 +166,7 @@ export default {
>
{{ deleteCacheAlertMessage }}
</gl-alert>
- <title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages">
+ <title-area :title="$options.i18n.pageTitle">
<template v-if="showDeleteDropdown" #right-actions>
<gl-dropdown
icon="ellipsis_v"
@@ -198,41 +183,34 @@ export default {
</gl-dropdown>
</template>
</title-area>
- <gl-alert
- v-if="!dependencyProxyAvailable"
- :dismissible="false"
- data-testid="proxy-not-available"
- >
- {{ $options.i18n.proxyNotAvailableText }}
- </gl-alert>
- <gl-skeleton-loader v-else-if="$apollo.queries.group.loading" />
-
- <div v-else data-testid="main-area">
- <gl-form-group :label="$options.i18n.proxyImagePrefix">
- <gl-form-input-group
- readonly
- :value="group.dependencyProxyImagePrefix"
- class="gl-layout-w-limited"
- data-testid="proxy-url"
- >
- <template #append>
- <clipboard-button
- :text="group.dependencyProxyImagePrefix"
- :title="$options.i18n.copyImagePrefixText"
- />
- </template>
- </gl-form-input-group>
- <template #description>
- <span data-qa-selector="dependency_proxy_count" data-testid="proxy-count">
- <gl-sprintf :message="$options.i18n.blobCountAndSize">
- <template #count>{{ group.dependencyProxyBlobCount }}</template>
- <template #size>{{ group.dependencyProxyTotalSize }}</template>
- </gl-sprintf>
- </span>
+ <gl-form-group v-if="showDependencyProxyImagePrefix" :label="$options.i18n.proxyImagePrefix">
+ <gl-form-input-group
+ readonly
+ :value="group.dependencyProxyImagePrefix"
+ class="gl-layout-w-limited"
+ data-testid="proxy-url"
+ >
+ <template #append>
+ <clipboard-button
+ :text="group.dependencyProxyImagePrefix"
+ :title="$options.i18n.copyImagePrefixText"
+ />
</template>
- </gl-form-group>
+ </gl-form-input-group>
+ <template #description>
+ <span data-qa-selector="dependency_proxy_count" data-testid="proxy-count">
+ <gl-sprintf :message="$options.i18n.blobCountAndSize">
+ <template #count>{{ group.dependencyProxyBlobCount }}</template>
+ <template #size>{{ group.dependencyProxyTotalSize }}</template>
+ </gl-sprintf>
+ </span>
+ </template>
+ </gl-form-group>
+ <gl-skeleton-loader v-if="$apollo.queries.group.loading" />
+
+ <div v-else data-testid="main-area">
<manifests-list
v-if="manifests && manifests.length"
:manifests="manifests"
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
index 78880b6e3f4..1bbd0c32dc4 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
@@ -1,5 +1,6 @@
<script>
-import { GlSprintf } from '@gitlab/ui';
+import { GlIcon, GlSprintf } from '@gitlab/ui';
+import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__ } from '~/locale';
@@ -7,6 +8,7 @@ import { s__ } from '~/locale';
export default {
name: 'ManifestRow',
components: {
+ GlIcon,
GlSprintf,
ListItem,
TimeagoTooltip,
@@ -24,17 +26,31 @@ export default {
version() {
return this.manifest?.imageName.split(':')[1];
},
+ isErrorStatus() {
+ return this.manifest?.status === MANIFEST_PENDING_DESTRUCTION_STATUS;
+ },
+ disabledRowStyle() {
+ return this.isErrorStatus ? 'gl-font-weight-normal gl-text-gray-500' : '';
+ },
},
i18n: {
cachedAgoMessage: s__('DependencyProxy|Cached %{time}'),
+ scheduledForDeletion: s__('DependencyProxy|Scheduled for deletion'),
},
};
</script>
<template>
- <list-item>
- <template #left-primary> {{ name }} </template>
- <template #left-secondary> {{ version }} </template>
+ <list-item :disabled="isErrorStatus">
+ <template #left-primary>
+ <span :class="disabledRowStyle">{{ name }}</span>
+ </template>
+ <template #left-secondary>
+ {{ version }}
+ <span v-if="isErrorStatus" class="gl-ml-4" data-testid="status"
+ ><gl-icon name="clock" /> {{ $options.i18n.scheduledForDeletion }}</span
+ >
+ </template>
<template #right-primary> &nbsp; </template>
<template #right-secondary>
<timeago-tooltip :time="manifest.createdAt" data-testid="cached-message">
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js
index 3c6ede6fdce..fdad69204ba 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js
@@ -1 +1,2 @@
export const GRAPHQL_PAGE_SIZE = 20;
+export const MANIFEST_PENDING_DESTRUCTION_STATUS = 'PENDING_DESTRUCTION';
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
index 5c43b10a5e3..c1597625964 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -20,6 +20,7 @@ query getDependencyProxyDetails(
id
createdAt
imageName
+ status
}
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
index dc73470e07d..14789aafdb7 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
import app from '~/packages_and_registries/dependency_proxy/app.vue';
import { apolloProvider } from '~/packages_and_registries/dependency_proxy/graphql';
import Translate from '~/vue_shared/translate';
@@ -11,12 +10,11 @@ export const initDependencyProxyApp = () => {
if (!el) {
return null;
}
- const { dependencyProxyAvailable, ...dataset } = el.dataset;
+ const { ...dataset } = el.dataset;
return new Vue({
el,
apolloProvider,
provide: {
- dependencyProxyAvailable: parseBoolean(dependencyProxyAvailable),
...dataset,
},
render(createElement) {
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
index f198d2e1bfa..425fb4596fd 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
@@ -191,7 +191,10 @@ export default {
<package-list-row
v-for="v in packageEntity.versions"
:key="v.id"
- :package-entity="{ name: packageEntity.name, ...v }"
+ :package-entity="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ name: packageEntity.name,
+ ...v,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:package-link="v.id.toString()"
:disable-delete="true"
:show-package-type="false"
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue
index c611f92036d..d3c38da1531 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue
@@ -33,7 +33,7 @@ export default {
<registry-search
:filter="filter"
:sorting="sorting"
- :tokens="[]"
+ :tokens="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */"
:sortable-fields="sortableFields"
@sorting:changed="updateSorting"
@filter:changed="setFilter"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
index 118c509828c..f5946797626 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlSprintf, GlBadge, GlResizeObserverDirective } from '@gitlab/ui';
+import { GlSprintf, GlBadge, GlResizeObserverDirective } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
@@ -14,7 +14,6 @@ export default {
name: 'PackageTitle',
components: {
TitleArea,
- GlIcon,
GlSprintf,
PackageTags,
MetadataItem,
@@ -84,7 +83,6 @@ export default {
data-qa-selector="package_title"
>
<template #sub-header>
- <gl-icon name="eye" class="gl-mr-3" />
<span data-testid="sub-header">
<gl-sprintf :message="$options.i18n.packageInfo">
<template #version>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
index 6222c2e73d7..04faff1a75b 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
@@ -1,5 +1,12 @@
<script>
-import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+ GlSprintf,
+ GlTooltipDirective,
+ GlTruncate,
+} from '@gitlab/ui';
import { s__, __ } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
@@ -17,7 +24,9 @@ import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
name: 'PackageListRow',
components: {
- GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
GlSprintf,
GlTruncate,
PackageTags,
@@ -50,31 +59,42 @@ export default {
pipelineUser() {
return this.pipeline?.user?.name;
},
- showWarningIcon() {
+ errorStatusRow() {
return this.packageEntity.status === PACKAGE_ERROR_STATUS;
},
showTags() {
return Boolean(this.packageEntity.tags?.nodes?.length);
},
- disabledRow() {
+ nonDefaultRow() {
return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
},
routerLinkEvent() {
- return this.disabledRow ? '' : 'click';
+ return this.nonDefaultRow ? '' : 'click';
+ },
+ errorPackageStyle() {
+ return {
+ 'gl-text-red-500': this.errorStatusRow,
+ 'gl-font-weight-normal': this.errorStatusRow,
+ };
},
},
i18n: {
erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'),
createdAt: __('Created %{timestamp}'),
+ deletePackage: s__('PackageRegistry|Delete package'),
+ errorPublishing: s__('PackageRegistry|Error publishing'),
+ warning: __('Warning'),
+ moreActions: __('More actions'),
},
};
</script>
<template>
- <list-item data-qa-selector="package_row" :disabled="disabledRow">
+ <list-item data-qa-selector="package_row">
<template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
<router-link
+ :class="errorPackageStyle"
class="gl-text-body gl-min-w-0"
data-testid="details-link"
data-qa-selector="package_link"
@@ -84,16 +104,6 @@ export default {
<gl-truncate :text="packageEntity.name" />
</router-link>
- <gl-button
- v-if="showWarningIcon"
- v-gl-tooltip="{ title: $options.i18n.erroredPackageText }"
- class="gl-hover-bg-transparent!"
- icon="warning"
- category="tertiary"
- data-testid="warning-icon"
- :aria-label="__('Warning')"
- />
-
<package-tags
v-if="showTags"
class="gl-ml-3"
@@ -104,7 +114,7 @@ export default {
</div>
</template>
<template #left-secondary>
- <div class="gl-display-flex" data-testid="left-secondary-infos">
+ <div v-if="!errorStatusRow" class="gl-display-flex" data-testid="left-secondary-infos">
<span>{{ packageEntity.version }}</span>
<div v-if="pipelineUser" class="gl-display-none gl-sm-display-flex gl-ml-2">
@@ -120,9 +130,19 @@ export default {
<package-path
v-if="isGroupPage"
:path="packageEntity.project.fullPath"
- :disabled="disabledRow"
+ :disabled="nonDefaultRow"
/>
</div>
+ <div v-else>
+ <gl-icon
+ v-gl-tooltip="{ title: $options.i18n.erroredPackageText }"
+ name="warning"
+ class="gl-text-red-500"
+ :aria-label="$options.i18n.warning"
+ data-testid="warning-icon"
+ />
+ <span class="gl-text-red-500">{{ $options.i18n.errorPublishing }}</span>
+ </div>
</template>
<template #right-primary>
@@ -139,16 +159,22 @@ export default {
</span>
</template>
- <template v-if="!disabledRow" #right-action>
- <gl-button
- data-testid="action-delete"
- icon="remove"
- category="secondary"
- variant="danger"
- :title="s__('PackageRegistry|Remove package')"
- :aria-label="s__('PackageRegistry|Remove package')"
- @click="$emit('packageToDelete', packageEntity)"
- />
+ <template v-if="packageEntity.canDestroy" #right-action>
+ <gl-dropdown
+ data-testid="delete-dropdown"
+ icon="ellipsis_v"
+ :text="$options.i18n.moreActions"
+ :text-sr-only="true"
+ category="tertiary"
+ no-caret
+ >
+ <gl-dropdown-item
+ data-testid="action-delete"
+ variant="danger"
+ @click="$emit('packageToDelete', packageEntity)"
+ >{{ $options.i18n.deletePackage }}</gl-dropdown-item
+ >
+ </gl-dropdown>
</template>
</list-item>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
index bf41c36e09b..440e11a99f2 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
@@ -27,21 +27,15 @@ export default {
packageAmountText() {
return n__(`%d Package`, `%d Packages`, this.count);
},
- infoMessages() {
- return [{ text: this.$options.i18n.LIST_INTRO_TEXT, link: this.helpUrl }];
- },
},
i18n: {
LIST_TITLE_TEXT: s__('PackageRegistry|Package Registry'),
- LIST_INTRO_TEXT: s__(
- 'PackageRegistry|Publish and share packages for a variety of common package managers. %{docLinkStart}More information%{docLinkEnd}',
- ),
},
};
</script>
<template>
- <title-area :title="$options.i18n.LIST_TITLE_TEXT" :info-messages="infoMessages">
+ <title-area :title="$options.i18n.LIST_TITLE_TEXT">
<template #metadata-amount>
<metadata-item v-if="showPackageCount" icon="package" :text="packageAmountText" />
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
index 298ed9bccdb..1aff23bc112 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
@@ -1,18 +1,20 @@
<script>
-import { GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { GlAlert, GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
+ PACKAGE_ERROR_STATUS,
} from '~/packages_and_registries/package_registry/constants';
import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
import Tracking from '~/tracking';
export default {
components: {
+ GlAlert,
GlKeysetPagination,
GlModal,
GlSprintf,
@@ -40,6 +42,7 @@ export default {
data() {
return {
itemToBeDeleted: null,
+ errorPackages: [],
};
},
computed: {
@@ -70,6 +73,24 @@ export default {
}
},
},
+ errorTitleAlert() {
+ return sprintf(
+ s__('PackageRegistry|There was an error publishing a %{packageName} package'),
+ { packageName: this.errorPackages[0].name },
+ );
+ },
+ showErrorPackageAlert() {
+ return this.errorPackages.length > 0;
+ },
+ },
+ watch: {
+ list(newVal) {
+ this.errorPackages = newVal.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS);
+ },
+ },
+ created() {
+ this.errorPackages =
+ this.list.length > 0 ? this.list.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS) : [];
},
methods: {
setItemToBeDeleted(item) {
@@ -83,12 +104,19 @@ export default {
deleteItemCanceled() {
this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION);
},
+ showConfirmationModal() {
+ this.setItemToBeDeleted(this.errorPackages[0]);
+ },
},
i18n: {
deleteModalContent: s__(
'PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?',
),
modalAction: s__('PackageRegistry|Delete package'),
+ errorMessageBodyAlert: s__(
+ 'PackageRegistry|There was a timeout and the package was not published. Delete this package and try again.',
+ ),
+ deleteThisPackage: s__('PackageRegistry|Delete this package'),
},
};
</script>
@@ -102,6 +130,14 @@ export default {
</div>
<template v-else>
+ <gl-alert
+ v-if="showErrorPackageAlert"
+ variant="danger"
+ :title="errorTitleAlert"
+ :primary-button-text="$options.i18n.deleteThisPackage"
+ @primaryAction="showConfirmationModal"
+ >{{ $options.i18n.errorMessageBodyAlert }}</gl-alert
+ >
<div data-qa-selector="packages-table">
<packages-list-row
v-for="packageEntity in list"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
index 66315fda9e9..b5695a01376 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
@@ -5,6 +5,7 @@ fragment PackageData on Package {
packageType
createdAt
status
+ canDestroy
tags {
nodes {
id
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index c45cbe56e00..41b0c285fff 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -1,4 +1,4 @@
-query getPackageDetails($id: ID!) {
+query getPackageDetails($id: PackagesPackageID!) {
package(id: $id) {
id
name
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
index 85a7aeb5561..482a3ef2ead 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
@@ -21,7 +21,6 @@ export default () => {
groupPath: el.dataset.groupPath,
groupDependencyProxyPath: el.dataset.groupDependencyProxyPath,
defaultExpanded: parseBoolean(el.dataset.defaultExpanded),
- dependencyProxyAvailable: parseBoolean(el.dataset.dependencyProxyAvailable),
},
render(createElement) {
return createElement(SettingsApp);
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
index fd62fe144b2..a5189201112 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
@@ -13,7 +13,6 @@ import {
import {
DEPENDENCY_PROXY_HEADER,
- DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
DEPENDENCY_PROXY_DOCS_PATH,
} from '~/packages_and_registries/settings/group/constants';
@@ -28,7 +27,6 @@ export default {
},
i18n: {
DEPENDENCY_PROXY_HEADER,
- DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
enabledProxyHelpText: s__(
'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}',
@@ -140,19 +138,6 @@ export default {
data-qa-selector="dependency_proxy_settings_content"
>
<template #title> {{ $options.i18n.DEPENDENCY_PROXY_HEADER }} </template>
- <template #description>
- <span data-testid="description">
- <gl-sprintf :message="$options.i18n.DEPENDENCY_PROXY_SETTINGS_DESCRIPTION">
- <template #docLink="{ content }">
- <gl-link
- data-testid="description-link"
- :href="$options.links.DEPENDENCY_PROXY_DOCS_PATH"
- >{{ content }}</gl-link
- >
- </template>
- </gl-sprintf>
- </span>
- </template>
<template #default>
<div>
<gl-toggle
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
index 64c12b4be6a..f285dfc0755 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
@@ -13,7 +13,7 @@ export default {
PackagesSettings,
DependencyProxySettings,
},
- inject: ['groupPath', 'dependencyProxyAvailable'],
+ inject: ['groupPath'],
apollo: {
group: {
query: getGroupPackagesSettingsQuery,
@@ -83,7 +83,6 @@ export default {
/>
<dependency-proxy-settings
- v-if="dependencyProxyAvailable"
:dependency-proxy-settings="dependencyProxySettings"
:dependency-proxy-image-ttl-policy="dependencyProxyImageTtlPolicy"
:is-loading="isLoading"
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
index ee922457993..0249b475e46 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
@@ -19,13 +19,10 @@ export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__(
);
export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy');
-export const DEPENDENCY_PROXY_SETTINGS_DESCRIPTION = s__(
- 'DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.',
-);
// Parameters
-export const PACKAGES_DOCS_PATH = helpPagePath('user/packages');
+export const PACKAGES_DOCS_PATH = helpPagePath('user/packages/index');
export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed';
export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex';
diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
index 3ef75b3ef0e..5ecacb84d65 100644
--- a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
+++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue
@@ -112,9 +112,7 @@ export default {
},
signupEnabledHelpText() {
const text = sprintf(
- s__(
- 'ApplicationSettings|When enabled, any user visiting %{host} will be able to create an account.',
- ),
+ s__('ApplicationSettings|Any user that visits %{host} can create an account.'),
{
host: this.host,
},
@@ -125,7 +123,7 @@ export default {
requireAdminApprovalHelpText() {
const text = sprintf(
s__(
- 'ApplicationSettings|When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.',
+ 'ApplicationSettings|Any user that visits %{host} and creates an account must be explicitly approved by an administrator before they can sign in. Only effective if sign-ups are enabled.',
),
{
host: this.host,
@@ -197,32 +195,34 @@ export default {
),
domainAllowListLabel: s__('ApplicationSettings|Allowed domains for sign-ups'),
domainAllowListDescription: s__(
- 'ApplicationSettings|ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com',
+ 'ApplicationSettings|Only users with e-mail addresses that match these domain(s) can sign up. Wildcards allowed. Use separate lines for multiple entries. Example: domain.com, *.domain.com',
),
userCapLabel: s__('ApplicationSettings|User cap'),
userCapDescription: s__(
- 'ApplicationSettings|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited.',
+ 'ApplicationSettings|After the instance reaches the user cap, any user who is added or requests access must be approved by an administrator. Leave blank for unlimited.',
),
domainDenyListGroupLabel: s__('ApplicationSettings|Domain denylist'),
- domainDenyListLabel: s__('ApplicationSettings|Enable domain denylist for sign ups'),
+ domainDenyListLabel: s__('ApplicationSettings|Enable domain denylist for sign-ups'),
domainDenyListTypeFileLabel: s__('ApplicationSettings|Upload denylist file'),
domainDenyListTypeRawLabel: s__('ApplicationSettings|Enter denylist manually'),
domainDenyListFileLabel: s__('ApplicationSettings|Denylist file'),
domainDenyListFileDescription: s__(
- 'ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.',
+ 'ApplicationSettings|Users with e-mail addresses that match these domain(s) cannot sign up. Wildcards allowed. Use separate lines or commas for multiple entries.',
),
domainDenyListListLabel: s__('ApplicationSettings|Denied domains for sign-ups'),
domainDenyListListDescription: s__(
- 'ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com',
+ 'ApplicationSettings|Users with e-mail addresses that match these domain(s) cannot sign up. Wildcards allowed. Use separate lines for multiple entries. Example: domain.com, *.domain.com',
),
domainPlaceholder: s__('ApplicationSettings|domain.com'),
emailRestrictionsEnabledGroupLabel: s__('ApplicationSettings|Email restrictions'),
emailRestrictionsEnabledLabel: s__(
- 'ApplicationSettings|Enable email restrictions for sign ups',
+ 'ApplicationSettings|Enable email restrictions for sign-ups',
),
emailRestrictionsGroupLabel: s__('ApplicationSettings|Email restrictions for sign-ups'),
- afterSignUpTextGroupLabel: s__('ApplicationSettings|After sign up text'),
- afterSignUpTextGroupDescription: s__('ApplicationSettings|Markdown enabled'),
+ afterSignUpTextGroupLabel: s__('ApplicationSettings|After sign-up text'),
+ afterSignUpTextGroupDescription: s__(
+ 'ApplicationSettings|Text shown after a user signs up. Markdown enabled.',
+ ),
},
};
</script>
@@ -288,19 +288,21 @@ export default {
name="application_setting[minimum_password_length]"
/>
- <gl-sprintf
- :message="
- s__(
- 'ApplicationSettings|See GitLab\'s %{linkStart}Password Policy Guidelines%{linkEnd}',
- )
- "
- >
- <template #link="{ content }">
- <gl-link :href="form.minimumPasswordLengthHelpLink" target="_blank">{{
- content
- }}</gl-link>
- </template>
- </gl-sprintf>
+ <template #description>
+ <gl-sprintf
+ :message="
+ s__(
+ 'ApplicationSettings|See GitLab\'s %{linkStart}Password Policy Guidelines%{linkEnd}.',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="form.minimumPasswordLengthHelpLink" target="_blank">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
</gl-form-group>
<gl-form-group
@@ -380,17 +382,19 @@ export default {
name="application_setting[email_restrictions]"
></textarea>
- <gl-sprintf
- :message="
- s__(
- 'ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. See the %{linkStart}supported syntax%{linkEnd} for more information.',
- )
- "
- >
- <template #link="{ content }">
- <gl-link :href="form.supportedSyntaxLinkUrl" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
+ <template #description>
+ <gl-sprintf
+ :message="
+ s__(
+ 'ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. %{linkStart}What is the supported syntax?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="form.supportedSyntaxLinkUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
</gl-form-group>
<gl-form-group
diff --git a/app/assets/javascripts/pages/admin/background_migrations/index.js b/app/assets/javascripts/pages/admin/background_migrations/index.js
new file mode 100644
index 00000000000..4c59613140b
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/background_migrations/index.js
@@ -0,0 +1,3 @@
+import { initBackgroundMigrationsApp } from '~/admin/background_migrations';
+
+initBackgroundMigrationsApp();
diff --git a/app/assets/javascripts/pages/admin/clusters/index.js b/app/assets/javascripts/pages/admin/clusters/index.js
deleted file mode 100644
index f398b1cee82..00000000000
--- a/app/assets/javascripts/pages/admin/clusters/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initCreateCluster from '~/create_cluster/init_create_cluster';
-
-initCreateCluster(document, gon);
diff --git a/app/assets/javascripts/pages/groups/clusters/index.js b/app/assets/javascripts/pages/groups/clusters/index.js
index 4d48bd4be2b..99fb7fa68a9 100644
--- a/app/assets/javascripts/pages/groups/clusters/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/index.js
@@ -1,5 +1,3 @@
import initIntegrationForm from '~/clusters/forms/show/index';
-import initCreateCluster from '~/create_cluster/init_create_cluster';
-initCreateCluster(document, gon);
initIntegrationForm();
diff --git a/app/assets/javascripts/pages/groups/new/components/app.vue b/app/assets/javascripts/pages/groups/new/components/app.vue
index c3ac074cd7a..713287f65b4 100644
--- a/app/assets/javascripts/pages/groups/new/components/app.vue
+++ b/app/assets/javascripts/pages/groups/new/components/app.vue
@@ -21,9 +21,7 @@ const PANELS = [
name: 'import-group-pane',
selector: '#import-group-pane',
title: s__('GroupsNew|Import group'),
- description: s__(
- 'GroupsNew|Export groups with all their related data and move to a new GitLab instance.',
- ),
+ description: s__('GroupsNew|Import a group and related data from another GitLab instance.'),
illustration: importGroupIllustration,
details: 'Migrate your existing groups from another instance of GitLab.',
},
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index 5d8ee146e62..52add416f38 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,24 +1,9 @@
import initVariableList from '~/ci_variable_list';
-import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners_filtered_search_token_keys';
import initSharedRunnersForm from '~/group_settings/mount_shared_runners';
-import { FILTERED_SEARCH } from '~/filtered_search/constants';
-import initFilteredSearch from '~/pages/search/init_filtered_search';
-import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
-import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSettingsPanels from '~/settings_panels';
// Initialize expandable settings panels
initSettingsPanels();
-initFilteredSearch({
- page: FILTERED_SEARCH.ADMIN_RUNNERS,
- filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
- anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
- useDefaultState: false,
-});
-
initSharedRunnersForm();
initVariableList();
-
-initInstallRunner();
-initRunnerAwsDeployments();
diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
index 9a4054eb110..35a8d3d979a 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
+++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue
@@ -9,6 +9,7 @@ import { getBulkImportsHistory } from '~/rest_api';
import ImportStatus from '~/import_entities/components/import_status.vue';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { DEFAULT_ERROR } from '../utils/error_messages';
@@ -16,6 +17,8 @@ const DEFAULT_PER_PAGE = 20;
const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1! gl-p-5!';
+const HISTORY_PAGINATION_SIZE_PERSIST_KEY = 'gl-bulk-imports-history-per-page';
+
const tableCell = (config) => ({
thClass: `${DEFAULT_TH_CLASSES}`,
tdClass: (value, key, item) => {
@@ -37,6 +40,7 @@ export default {
PaginationBar,
ImportStatus,
TimeAgo,
+ LocalStorageSync,
},
data() {
@@ -59,7 +63,7 @@ export default {
}),
tableCell({
key: 'destination_name',
- label: s__('BulkImport|New group'),
+ label: s__('BulkImport|Destination group'),
thClass: `${DEFAULT_TH_CLASSES} gl-w-40p`,
}),
tableCell({
@@ -85,10 +89,13 @@ export default {
this.loadHistoryItems();
},
deep: true,
- immediate: true,
},
},
+ mounted() {
+ this.loadHistoryItems();
+ },
+
methods: {
async loadHistoryItems() {
try {
@@ -116,6 +123,7 @@ export default {
},
gitlabLogo: window.gon.gitlab_logo,
+ historyPaginationSizePersistKey: HISTORY_PAGINATION_SIZE_PERSIST_KEY,
};
</script>
@@ -171,5 +179,9 @@ export default {
@set-page-size="paginationConfig.perPage = $event"
/>
</template>
+ <local-storage-sync
+ v-model="paginationConfig.perPage"
+ :storage-key="$options.historyPaginationSizePersistKey"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/clusters/index.js b/app/assets/javascripts/pages/projects/clusters/index.js
deleted file mode 100644
index f398b1cee82..00000000000
--- a/app/assets/javascripts/pages/projects/clusters/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initCreateCluster from '~/create_cluster/init_create_cluster';
-
-initCreateCluster(document, gon);
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index 71ab5a0b19c..0b34f374abc 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,6 +1,6 @@
import ClustersBundle from '~/clusters/clusters_bundle';
import initIntegrationForm from '~/clusters/forms/show';
-import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
+import initGkeNamespace from '~/clusters/gke_cluster_namespace';
import initClusterHealth from './cluster_health';
new ClustersBundle(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
index 1667f2c3576..1912477758b 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
@@ -1,5 +1,6 @@
<script>
-import { GlLink, GlIcon, GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+import { GlLink, GlIcon, GlButton, GlPopover, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import { isExperimentVariant } from '~/experimentation/utils';
import eventHub from '~/invite_members/event_hub';
@@ -12,6 +13,7 @@ export default {
GlLink,
GlIcon,
GlButton,
+ GlPopover,
GitlabExperiment,
},
directives: {
@@ -19,6 +21,8 @@ export default {
},
i18n: {
trialOnly: s__('LearnGitlab|Trial only'),
+ contactAdmin: s__('LearnGitlab|Contact your administrator to start a free Ultimate trial.'),
+ viewAdminList: s__('LearnGitlab|View administrator list'),
watchHow: __('Watch how'),
},
props: {
@@ -31,6 +35,11 @@ export default {
type: Object,
},
},
+ data() {
+ return {
+ popoverId: uniqueId('contact-admin-'),
+ };
+ },
computed: {
linkTitle() {
return ACTION_LABELS[this.action].title;
@@ -78,7 +87,7 @@ export default {
{{ linkTitle }}
</gl-link>
<gl-link
- v-else
+ v-else-if="value.enabled"
:target="openInNewTab ? '_blank' : '_self'"
:href="value.url"
data-testid="uncompleted-learn-gitlab-link"
@@ -87,6 +96,33 @@ export default {
>
{{ linkTitle }}
</gl-link>
+ <template v-else>
+ <div data-testid="disabled-learn-gitlab-link">{{ linkTitle }}</div>
+ <gl-button
+ :id="popoverId"
+ category="tertiary"
+ icon="question-o"
+ class="ml-auto"
+ :aria-label="$options.i18n.contactAdmin"
+ size="small"
+ data-testid="contact-admin-popover-trigger"
+ />
+ <gl-popover
+ :target="popoverId"
+ placement="top"
+ triggers="hover focus"
+ data-testid="contact-admin-popover"
+ >
+ <p>{{ $options.i18n.contactAdmin }}</p>
+ <gl-link
+ :href="value.url"
+ class="font-size-inherit"
+ data-testid="view-administrator-link-text"
+ >
+ {{ $options.i18n.viewAdminList }}
+ </gl-link>
+ </gl-popover>
+ </template>
<gitlab-experiment name="video_tutorials_continuous_onboarding">
<template #control></template>
<template #candidate>
@@ -100,6 +136,7 @@ export default {
:href="linkToVideoTutorial"
target="_blank"
class="ml-auto"
+ size="small"
data-testid="video-tutorial-link"
data-track-action="click_video_link"
:data-track-label="linkTitle"
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
index 9ba5e17237a..05bacd9b350 100644
--- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
+++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
@@ -30,6 +30,7 @@ export const ACTION_LABELS = {
description: s__('LearnGitLab|Try all GitLab features for 30 days, no credit card required.'),
section: 'workspace',
position: 3,
+ openInNewTab: true,
},
codeOwnersEnabled: {
title: s__('LearnGitLab|Add code owners'),
@@ -40,6 +41,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 4,
+ openInNewTab: true,
videoTutorial: 'https://vimeo.com/670896787',
},
requiredMrApprovalsEnabled: {
@@ -49,6 +51,7 @@ export const ACTION_LABELS = {
trialRequired: true,
section: 'workspace',
position: 5,
+ openInNewTab: true,
videoTutorial: 'https://vimeo.com/670904904',
},
mergeRequestCreated: {
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index 0e0c1475eda..48e360ce762 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -8,6 +8,7 @@ import createDefaultClient from '~/lib/graphql';
import initSourcegraph from '~/sourcegraph';
import ZenMode from '~/zen_mode';
import initAwardsApp from '~/emoji/awards_app';
+import MrWidgetHowToMergeModal from '~/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue';
import getStateQuery from './queries/get_state.query.graphql';
export default function initMergeRequestShow() {
@@ -19,22 +20,45 @@ export default function initMergeRequestShow() {
initAwardsApp(document.getElementById('js-vue-awards-block'));
const el = document.querySelector('.js-mr-status-box');
+ const { iid, issuableType, projectPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
// eslint-disable-next-line no-new
new Vue({
el,
+ name: 'IssuableStatusBoxRoot',
apolloProvider,
provide: {
query: getStateQuery,
- projectPath: el.dataset.projectPath,
- iid: el.dataset.iid,
+ iid,
+ projectPath,
},
render(h) {
return h(StatusBox, {
props: {
initialState: el.dataset.state,
+ issuableType,
+ },
+ });
+ },
+ });
+
+ const modalEl = document.getElementById('js-check-out-modal');
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: modalEl,
+ render(h) {
+ return h(MrWidgetHowToMergeModal, {
+ props: {
+ canMerge: modalEl.dataset.canMerge === 'true',
+ isFork: modalEl.dataset.isFork === 'true',
+ sourceBranch: modalEl.dataset.sourceBranch,
+ sourceProjectPath: modalEl.dataset.sourceProjectPath,
+ targetBranch: modalEl.dataset.targetBranch,
+ sourceProjectDefaultUrl: modalEl.dataset.sourceProjectDefaultUrl,
+ reviewingDocsPath: modalEl.dataset.reviewingDocsPath,
},
});
},
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index 4f57e1308df..032e2410233 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -65,6 +65,7 @@ export default class Project {
const fieldName = $dropdown.data('fieldName');
const shouldVisit = Boolean($dropdown.data('visit'));
const $form = $dropdown.closest('form');
+ const path = $form.find('#path').val();
const action = $form.attr('action');
const linkTarget = mergeUrlParams(serializeForm($form[0]), action);
@@ -116,20 +117,21 @@ export default class Project {
},
clicked(options) {
const { e } = options;
- e.preventDefault();
- // Since this page does not reload when changing directories in a repo
- // the rendered links do not have the path to the current directory.
- // This updates the path based on the current url and then opens
- // the the url with the updated path parameter.
- if (shouldVisit) {
+ if (!shouldVisit) {
+ e.preventDefault();
+ }
+
+ // Some pages need to dynamically get the current path
+ // so they can opt-in to JS getting the path from the
+ // current URL by not setting a path in the dropdown form
+ if (shouldVisit && path === undefined) {
+ e.preventDefault();
+
const selectedUrl = new URL(e.target.href);
const loc = window.location.href;
if (loc.includes('/-/')) {
- // Since the current ref in renderRow is outdated on page changes
- // (To be addressed in: https://gitlab.com/gitlab-org/gitlab/-/issues/327085)
- // We are deciphering the current ref from the dropdown data instead
const currentRef = $dropdown.data('ref');
// The split and startWith is to ensure an exact word match
// and avoid partial match ie. currentRef is "dev" and loc is "development"
diff --git a/app/assets/javascripts/pages/projects/serverless/index.js b/app/assets/javascripts/pages/projects/serverless/index.js
deleted file mode 100644
index 9ae81b327b1..00000000000
--- a/app/assets/javascripts/pages/projects/serverless/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import ServerlessBundle from '~/serverless/serverless_bundle';
-
-new ServerlessBundle(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js
index 2048d3dfc37..64df0d07d74 100644
--- a/app/assets/javascripts/pages/projects/services/edit/index.js
+++ b/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -1,5 +1,4 @@
import initIntegrationSettingsForm from '~/integrations/edit';
-import PrometheusAlerts from '~/prometheus_alerts';
import CustomMetrics from '~/prometheus_metrics/custom_metrics';
initIntegrationSettingsForm();
@@ -10,5 +9,3 @@ if (prometheusSettingsWrapper) {
const customMetrics = new CustomMetrics(prometheusSettingsSelector);
customMetrics.init();
}
-
-PrometheusAlerts();
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 71c6773c176..e2b1a702560 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,8 +1,5 @@
-import initTree from 'ee_else_ce/repository';
-import Activities from '~/activities';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
-import { BlobViewer } from '~/blob/viewer';
-import { initUploadForm } from '~/blob_edit/blob_bundle';
+
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import leaveByUrl from '~/namespaces/leave_by_url';
@@ -10,33 +7,38 @@ import initVueNotificationsDropdown from '~/notifications';
import Star from '~/projects/star';
import { initUploadFileTrigger } from '~/projects/upload_file';
import initReadMore from '~/read_more';
-import UserCallout from '~/user_callout';
-
-initReadMore();
-new Star(); // eslint-disable-line no-new
-
-// eslint-disable-next-line no-new
-new UserCallout({
- setCalloutPerProject: false,
- className: 'js-autodevops-banner',
-});
// Project show page loads different overview content based on user preferences
-
if (document.querySelector('.js-upload-blob-form')) {
- initUploadForm();
+ import(/* webpackChunkName: 'blobBundle' */ '~/blob_edit/blob_bundle')
+ .then(({ initUploadForm }) => {
+ initUploadForm();
+ })
+ .catch(() => {});
}
if (document.getElementById('js-tree-list')) {
- initTree();
+ import(/* webpackChunkName: 'treeList' */ 'ee_else_ce/repository')
+ .then(({ default: initTree }) => {
+ initTree();
+ })
+ .catch(() => {});
}
if (document.querySelector('.blob-viewer')) {
- new BlobViewer(); // eslint-disable-line no-new
+ import(/* webpackChunkName: 'blobViewer' */ '~/blob/viewer')
+ .then(({ BlobViewer }) => {
+ new BlobViewer(); // eslint-disable-line no-new
+ })
+ .catch(() => {});
}
if (document.querySelector('.project-show-activity')) {
- new Activities(); // eslint-disable-line no-new
+ import(/* webpackChunkName: 'activitiesList' */ '~/activities')
+ .then(({ default: Activities }) => {
+ new Activities(); // eslint-disable-line no-new
+ })
+ .catch(() => {});
}
leaveByUrl('project');
@@ -48,3 +50,18 @@ new ShortcutsNavigation(); // eslint-disable-line no-new
initUploadFileTrigger();
initInviteMembersModal();
initInviteMembersTrigger();
+
+initReadMore();
+new Star(); // eslint-disable-line no-new
+
+if (document.querySelector('.js-autodevops-banner')) {
+ import(/* webpackChunkName: 'userCallOut' */ '~/user_callout')
+ .then(({ default: UserCallout }) => {
+ // eslint-disable-next-line no-new
+ new UserCallout({
+ setCalloutPerProject: false,
+ className: 'js-autodevops-banner',
+ });
+ })
+ .catch(() => {});
+}
diff --git a/app/assets/javascripts/pages/projects/wikis/edit/index.js b/app/assets/javascripts/pages/projects/wikis/edit/index.js
deleted file mode 100644
index b2288c2655c..00000000000
--- a/app/assets/javascripts/pages/projects/wikis/edit/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { mountApplications } from '~/pages/shared/wikis/edit';
-
-mountApplications();
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index 83fcd348ddf..692baee383b 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -1,3 +1,6 @@
import Wikis from '~/pages/shared/wikis/wikis';
+import { mountApplications } from '~/pages/shared/wikis/async_edit';
+
+mountApplications();
export default new Wikis();
diff --git a/app/assets/javascripts/pages/projects/wikis/show/index.js b/app/assets/javascripts/pages/projects/wikis/show/index.js
index 7ca5f6964cd..288f6b616cc 100644
--- a/app/assets/javascripts/pages/projects/wikis/show/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/show/index.js
@@ -1,5 +1,3 @@
import { mountApplications } from '~/pages/shared/wikis/show';
-import { mountApplications as mountEditApplications } from '~/pages/shared/wikis/async_edit';
mountApplications();
-mountEditApplications();
diff --git a/app/assets/javascripts/pages/registrations/new/index.js b/app/assets/javascripts/pages/registrations/new/index.js
index 8bbe81a9ed5..94a5c1cb29b 100644
--- a/app/assets/javascripts/pages/registrations/new/index.js
+++ b/app/assets/javascripts/pages/registrations/new/index.js
@@ -3,9 +3,14 @@ import { trackNewRegistrations } from '~/google_tag_manager';
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
+import Tracking from '~/tracking';
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
trackNewRegistrations();
+
+Tracking.enableFormTracking({
+ forms: { allow: ['new_user'] },
+});
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index dee832c01d5..100ffc0664b 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -13,6 +13,7 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-approaching-seats-count-threshold',
'.js-storage-enforcement-banner',
'.js-user-over-limit-free-plan-alert',
+ '.js-minute-limit-banner',
];
const initCallouts = () => {
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
index 8536db78dfb..d9da238358f 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
@@ -54,15 +54,15 @@ export default {
return {
message: this.defaultMessage,
openMergeRequest: false,
- targetBranch: this.currentBranch,
+ sourceBranch: this.currentBranch,
};
},
computed: {
isCommitFormFilledOut() {
- return this.message && this.targetBranch;
+ return this.message && this.sourceBranch;
},
- isCurrentBranchTarget() {
- return this.targetBranch === this.currentBranch;
+ isCurrentBranchSourceBranch() {
+ return this.sourceBranch === this.currentBranch;
},
isSubmitDisabled() {
return !this.isCommitFormFilledOut || (!this.hasUnsavedChanges && !this.isNewCiConfigFile);
@@ -79,7 +79,7 @@ export default {
onSubmit() {
this.$emit('submit', {
message: this.message,
- targetBranch: this.targetBranch,
+ sourceBranch: this.sourceBranch,
openMergeRequest: this.openMergeRequest,
});
},
@@ -93,7 +93,7 @@ export default {
},
i18n: {
commitMessage: __('Commit message'),
- targetBranch: __('Target Branch'),
+ sourceBranch: __('Branch'),
startMergeRequest: __('Start a %{new_merge_request} with these changes'),
newMergeRequest: __('new merge request'),
commitChanges: __('Commit changes'),
@@ -120,20 +120,20 @@ export default {
/>
</gl-form-group>
<gl-form-group
- id="target-branch-group"
- :label="$options.i18n.targetBranch"
+ id="source-branch-group"
+ :label="$options.i18n.sourceBranch"
label-cols-sm="2"
- label-for="target-branch-field"
+ label-for="source-branch-field"
>
<gl-form-input
- id="target-branch-field"
- v-model="targetBranch"
+ id="source-branch-field"
+ v-model="sourceBranch"
class="gl-font-monospace!"
required
- data-qa-selector="target_branch_field"
+ data-qa-selector="source_branch_field"
/>
<gl-form-checkbox
- v-if="!isCurrentBranchTarget"
+ v-if="!isCurrentBranchSourceBranch"
v-model="openMergeRequest"
data-testid="new-mr-checkbox"
data-qa-selector="new_mr_checkbox"
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
index 4ef598d6ff3..9cbf60b1c8f 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
@@ -75,7 +75,7 @@ export default {
},
},
methods: {
- async onCommitSubmit({ message, targetBranch, openMergeRequest }) {
+ async onCommitSubmit({ message, sourceBranch, openMergeRequest }) {
this.isSaving = true;
try {
@@ -88,7 +88,7 @@ export default {
variables: {
action: this.action,
projectPath: this.projectFullPath,
- branch: targetBranch,
+ branch: sourceBranch,
startBranch: this.currentBranch,
message,
filePath: this.ciConfigPath,
@@ -104,12 +104,11 @@ export default {
if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else {
- const commitBranch = targetBranch;
const params = openMergeRequest
? {
type: COMMIT_SUCCESS_WITH_REDIRECT,
params: {
- sourceBranch: commitBranch,
+ sourceBranch,
targetBranch: this.currentBranch,
},
}
@@ -119,10 +118,10 @@ export default {
...params,
});
- this.updateLastCommitBranch(targetBranch);
- this.updateCurrentBranch(targetBranch);
+ this.updateLastCommitBranch(sourceBranch);
+ this.updateCurrentBranch(sourceBranch);
- if (this.currentBranch === targetBranch) {
+ if (this.currentBranch === sourceBranch) {
this.$emit('updateCommitSha');
}
}
diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
index 853e839a7ab..42e2d34fa3a 100644
--- a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
+++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
@@ -46,7 +46,9 @@ export default {
:value="mergedYaml"
:file-name="ciConfigPath"
:file-global-id="fileGlobalId"
- :editor-options="{ readOnly: true }"
+ :editor-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ readOnly: true,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
v-on="$listeners"
/>
</div>
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
index 83b074dd55c..58df98d0fb7 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
@@ -1,26 +1,74 @@
<script>
+import { GlButton } from '@gitlab/ui';
+import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_LOADING } from '../../constants';
+import FileTreePopover from '../popovers/file_tree_popover.vue';
import BranchSwitcher from './branch_switcher.vue';
export default {
components: {
BranchSwitcher,
+ FileTreePopover,
+ GlButton,
},
+ mixins: [glFeatureFlagMixin()],
props: {
hasUnsavedChanges: {
type: Boolean,
required: false,
default: false,
},
+ isNewCiConfigFile: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
shouldLoadNewBranch: {
type: Boolean,
required: false,
default: false,
},
},
+ apollo: {
+ appStatus: {
+ query: getAppStatus,
+ update(data) {
+ return data.app.status;
+ },
+ },
+ },
+ computed: {
+ isAppLoading() {
+ return this.appStatus === EDITOR_APP_STATUS_LOADING;
+ },
+ showFileTreeToggle() {
+ return (
+ this.glFeatures.pipelineEditorFileTree &&
+ !this.isNewCiConfigFile &&
+ this.appStatus !== EDITOR_APP_STATUS_EMPTY
+ );
+ },
+ },
+ methods: {
+ onFileTreeBtnClick() {
+ this.$emit('toggle-file-tree');
+ },
+ },
};
</script>
<template>
<div class="gl-mb-4">
+ <gl-button
+ v-if="showFileTreeToggle"
+ id="file-tree-toggle"
+ icon="file-tree"
+ data-testid="file-tree-toggle"
+ :aria-label="__('File Tree')"
+ :loading="isAppLoading"
+ @click="onFileTreeBtnClick"
+ />
+ <file-tree-popover v-if="showFileTreeToggle" />
<branch-switcher
:has-unsaved-changes="hasUnsavedChanges"
:should-load-new-branch="shouldLoadNewBranch"
diff --git a/app/assets/javascripts/pipeline_editor/components/file_tree/container.vue b/app/assets/javascripts/pipeline_editor/components/file_tree/container.vue
new file mode 100644
index 00000000000..280cd729a43
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/file_tree/container.vue
@@ -0,0 +1,78 @@
+<script>
+import { GlAlert, GlTooltipDirective } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { FILE_TREE_TIP_DISMISSED_KEY } from '../../constants';
+import FileItem from './file_item.vue';
+
+const i18n = {
+ tipBtn: __('Learn more'),
+ tipDescription: s__(
+ 'PipelineEditorFileTree|When you use the include keyword to add pipeline configuration from files in the project, those files will be listed here.',
+ ),
+ tipTitle: s__('PipelineEditorFileTree|Configuration files added with the include keyword'),
+};
+
+export default {
+ i18n,
+ name: 'PipelineEditorFileTreeContainer',
+ components: {
+ FileIcon,
+ FileItem,
+ GlAlert,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['ciConfigPath', 'includesHelpPagePath'],
+ props: {
+ includes: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ canShowTip: localStorage.getItem(FILE_TREE_TIP_DISMISSED_KEY) !== 'true',
+ };
+ },
+ computed: {
+ showTip() {
+ return this.includes.length === 0 && this.canShowTip;
+ },
+ },
+ methods: {
+ dismissTip() {
+ this.canShowTip = false;
+ localStorage.setItem(FILE_TREE_TIP_DISMISSED_KEY, 'true');
+ },
+ },
+};
+</script>
+<template>
+ <aside class="file-tree-container gl-mr-5 gl-mb-5">
+ <div
+ v-gl-tooltip
+ :title="ciConfigPath"
+ class="gl-bg-gray-50 gl-py-2 gl-px-3 gl-mb-3 gl-rounded-base"
+ >
+ <span class="file-row-name gl-str-truncated" :title="ciConfigPath">
+ <file-icon class="file-row-icon" :file-name="ciConfigPath" />
+ <span data-testid="current-config-filename">{{ ciConfigPath }}</span>
+ </span>
+ </div>
+ <gl-alert
+ v-if="showTip"
+ variant="tip"
+ :title="$options.i18n.tipTitle"
+ :secondary-button-text="$options.i18n.tipBtn"
+ :secondary-button-link="includesHelpPagePath"
+ @dismiss="dismissTip"
+ >
+ {{ $options.i18n.tipDescription }}
+ </gl-alert>
+ <div class="gl-overflow-y-auto">
+ <file-item v-for="file in includes" :key="file.location" :file="file" />
+ </div>
+ </aside>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/file_tree/file_item.vue b/app/assets/javascripts/pipeline_editor/components/file_tree/file_item.vue
new file mode 100644
index 00000000000..786d483b5b9
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/file_tree/file_item.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+
+export default {
+ name: 'PipelineEditorFileItem',
+ components: {
+ FileIcon,
+ GlIcon,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ fileName() {
+ return this.file.location;
+ },
+ filePath() {
+ return this.file.blob || this.file.raw;
+ },
+ },
+};
+</script>
+<template>
+ <gl-link
+ v-gl-tooltip
+ :href="filePath"
+ :title="fileName"
+ target="_blank"
+ class="file-tree-includes-link gl-display-flex gl-justify-content-space-between gl-hover-bg-gray-50 gl-text-body gl-hover-text-gray-900 gl-hover-text-decoration-none gl-py-2 gl-px-3 gl-rounded-base"
+ >
+ <span class="file-row-name gl-str-truncated" :title="fileName">
+ <file-icon class="file-row-icon" :file-name="fileName" />
+ <span>{{ fileName }}</span>
+ </span>
+ <gl-icon class="gl-display-none gl-relative gl-text-gray-500" name="external-link" />
+ </gl-link>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
index 25a78aab933..7beabcfe403 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
@@ -47,6 +47,12 @@ export default {
downstreamPipelines() {
return this.linkedPipelines?.downstream?.nodes || [];
},
+ hasDownstreamPipelines() {
+ return this.downstreamPipelines.length > 0;
+ },
+ hasPipelineStages() {
+ return this.pipelineStages.length > 0;
+ },
pipelinePath() {
return this.pipeline.detailedStatus?.detailsPath || '';
},
@@ -73,9 +79,6 @@ export default {
};
});
},
- showDownstreamPipelines() {
- return this.downstreamPipelines.length > 0;
- },
upstreamPipeline() {
return this.linkedPipelines?.upstream;
},
@@ -84,15 +87,20 @@ export default {
</script>
<template>
- <div v-if="pipelineStages.length > 0" class="stage-cell gl-mr-5">
+ <div
+ v-if="hasPipelineStages"
+ class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell gl-mr-5"
+ >
<linked-pipelines-mini-list
v-if="upstreamPipeline"
- :triggered-by="[upstreamPipeline]"
+ :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ upstreamPipeline,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
data-testid="pipeline-editor-mini-graph-upstream"
/>
- <pipeline-mini-graph class="gl-display-inline" :stages="pipelineStages" />
+ <pipeline-mini-graph :stages="pipelineStages" />
<linked-pipelines-mini-list
- v-if="showDownstreamPipelines"
+ v-if="hasDownstreamPipelines"
:triggered="downstreamPipelines"
:pipeline-path="pipelinePath"
data-testid="pipeline-editor-mini-graph-downstream"
diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
index d50e6f9a623..da31fc62d09 100644
--- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -23,7 +23,7 @@ import CiEditorHeader from './editor/ci_editor_header.vue';
import TextEditor from './editor/text_editor.vue';
import CiLint from './lint/ci_lint.vue';
import EditorTab from './ui/editor_tab.vue';
-import WalkthroughPopover from './walkthrough_popover.vue';
+import WalkthroughPopover from './popovers/walkthrough_popover.vue';
export default {
i18n: {
diff --git a/app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue b/app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue
new file mode 100644
index 00000000000..6270429535d
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlLink, GlPopover, GlOutsideDirective as Outside, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { FILE_TREE_POPOVER_DISMISSED_KEY } from '../../constants';
+
+export default {
+ name: 'PipelineEditorFileTreePopover',
+ directives: { Outside },
+ i18n: {
+ description: s__(
+ 'pipelineEditorWalkthrough|You can use the file tree to view your pipeline configuration files. %{linkStart}Learn more%{linkEnd}',
+ ),
+ },
+ components: {
+ GlLink,
+ GlPopover,
+ GlSprintf,
+ },
+ inject: ['includesHelpPagePath'],
+ data() {
+ return {
+ showPopover: false,
+ };
+ },
+ mounted() {
+ this.showPopover = localStorage.getItem(FILE_TREE_POPOVER_DISMISSED_KEY) !== 'true';
+ },
+ methods: {
+ closePopover() {
+ this.showPopover = false;
+ },
+ dismissPermanently() {
+ this.closePopover();
+ localStorage.setItem(FILE_TREE_POPOVER_DISMISSED_KEY, 'true');
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover
+ v-if="showPopover"
+ show
+ show-close-button
+ target="file-tree-toggle"
+ triggers="manual"
+ placement="right"
+ data-qa-selector="file_tree_popover"
+ @close-button-clicked="dismissPermanently"
+ >
+ <div v-outside="closePopover" class="gl-font-base gl-mb-3">
+ <gl-sprintf :message="$options.i18n.description">
+ <template #link="{ content }">
+ <gl-link :href="includesHelpPagePath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue b/app/assets/javascripts/pipeline_editor/components/popovers/walkthrough_popover.vue
index 5742b11b841..c636d8b8e34 100644
--- a/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue
+++ b/app/assets/javascripts/pipeline_editor/components/popovers/walkthrough_popover.vue
@@ -76,7 +76,7 @@ export default {
@click="handleClickCta"
>
<gl-emoji data-name="rocket" />
- {{ this.$options.i18n.ctaText }}
+ {{ $options.i18n.ctaText }}
</gl-button>
</div>
</gl-popover>
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index 9b4732b26d2..ff7c742f588 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -49,6 +49,10 @@ export const BRANCH_PAGINATION_LIMIT = 20;
export const BRANCH_SEARCH_DEBOUNCE = '500';
export const SOURCE_EDITOR_DEBOUNCE = 500;
+export const FILE_TREE_DISPLAY_KEY = 'pipeline_editor_file_tree_display';
+export const FILE_TREE_POPOVER_DISMISSED_KEY = 'pipeline_editor_file_tree_popover_dismissed';
+export const FILE_TREE_TIP_DISMISSED_KEY = 'pipeline_editor_file_tree_tip_dismissed';
+
export const STARTER_TEMPLATE_NAME = 'Getting-Started';
export const pipelineEditorTrackingOptions = {
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql
index df7de6a1f54..5354ed7c2d5 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql
@@ -3,6 +3,12 @@
query getCiConfigData($projectPath: ID!, $sha: String, $content: String!) {
ciConfig(projectPath: $projectPath, sha: $sha, content: $content) {
errors
+ includes {
+ location
+ type
+ blob
+ raw
+ }
mergedYaml
status
stages {
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 732fc665c9e..e13d9cf9df0 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -30,6 +30,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
defaultBranch,
emptyStateIllustrationPath,
helpPaths,
+ includesHelpPagePath,
lintHelpPagePath,
lintUnavailableHelpPagePath,
needsHelpPagePath,
@@ -41,7 +42,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
runnerHelpPagePath,
totalBranches,
ymlHelpPagePath,
- } = el?.dataset;
+ } = el.dataset;
const configurationPaths = Object.fromEntries(
Object.entries(CODE_SNIPPET_SOURCE_SETTINGS).map(([source, { datasetKey }]) => [
@@ -118,6 +119,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
defaultBranch,
emptyStateIllustrationPath,
helpPaths,
+ includesHelpPagePath,
lintHelpPagePath,
lintUnavailableHelpPagePath,
needsHelpPagePath,
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 4e6a4ffa6d2..3fd31edec2c 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -104,6 +104,7 @@ export default {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
}
+ this.isNewCiConfigFile = false;
if (!hasCIFile) {
if (this.shouldSkipStartScreen) {
this.setNewEmptyCiConfigFile();
@@ -381,7 +382,7 @@ export default {
</script>
<template>
- <div class="gl-mt-4 gl-relative">
+ <div class="gl-mt-4 gl-relative" data-qa-selector="pipeline_editor_app">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<pipeline-editor-empty-state
v-else-if="showStartScreen"
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
index 23e3ce10d5a..59022a91322 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
@@ -1,12 +1,14 @@
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue';
import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue';
+import PipelineEditorFileTree from './components/file_tree/container.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
-import { CREATE_TAB } from './constants';
+import { CREATE_TAB, FILE_TREE_DISPLAY_KEY } from './constants';
export default {
commitSectionRef: 'commitSectionRef',
@@ -28,9 +30,11 @@ export default {
GlModal,
PipelineEditorDrawer,
PipelineEditorFileNav,
+ PipelineEditorFileTree,
PipelineEditorHeader,
PipelineEditorTabs,
},
+ mixins: [glFeatureFlagMixin()],
props: {
ciConfigData: {
type: Object,
@@ -61,6 +65,7 @@ export default {
scrollToCommitForm: false,
shouldLoadNewBranch: false,
showDrawer: false,
+ showFileTree: false,
showSwitchBranchModal: false,
};
},
@@ -68,6 +73,15 @@ export default {
showCommitForm() {
return this.currentTab === CREATE_TAB;
},
+ includesFiles() {
+ return this.ciConfigData?.includes || [];
+ },
+ isFileTreeVisible() {
+ return this.showFileTree && this.glFeatures.pipelineEditorFileTree;
+ },
+ },
+ mounted() {
+ this.showFileTree = JSON.parse(localStorage.getItem(FILE_TREE_DISPLAY_KEY)) || false;
},
methods: {
closeBranchModal() {
@@ -82,6 +96,10 @@ export default {
openDrawer() {
this.showDrawer = true;
},
+ toggleFileTree() {
+ this.showFileTree = !this.showFileTree;
+ localStorage.setItem(FILE_TREE_DISPLAY_KEY, this.showFileTree);
+ },
switchBranch() {
this.showSwitchBranchModal = false;
this.shouldLoadNewBranch = true;
@@ -114,28 +132,39 @@ export default {
</gl-modal>
<pipeline-editor-file-nav
:has-unsaved-changes="hasUnsavedChanges"
+ :is-new-ci-config-file="isNewCiConfigFile"
:should-load-new-branch="shouldLoadNewBranch"
@select-branch="handleConfirmSwitchBranch"
+ @toggle-file-tree="toggleFileTree"
v-on="$listeners"
/>
- <pipeline-editor-header
- :ci-config-data="ciConfigData"
- :commit-sha="commitSha"
- :is-new-ci-config-file="isNewCiConfigFile"
- v-on="$listeners"
- />
- <pipeline-editor-tabs
- :ci-config-data="ciConfigData"
- :ci-file-content="ciFileContent"
- :commit-sha="commitSha"
- :is-new-ci-config-file="isNewCiConfigFile"
- :show-drawer="showDrawer"
- v-on="$listeners"
- @open-drawer="openDrawer"
- @close-drawer="closeDrawer"
- @set-current-tab="setCurrentTab"
- @walkthrough-popover-cta-clicked="setScrollToCommitForm"
- />
+ <div class="gl-display-flex gl-w-full gl-sm-flex-direction-column">
+ <pipeline-editor-file-tree
+ v-if="isFileTreeVisible"
+ class="gl-flex-shrink-0"
+ :includes="includesFiles"
+ />
+ <div class="gl-flex-grow-1 gl-min-w-0">
+ <pipeline-editor-header
+ :ci-config-data="ciConfigData"
+ :commit-sha="commitSha"
+ :is-new-ci-config-file="isNewCiConfigFile"
+ v-on="$listeners"
+ />
+ <pipeline-editor-tabs
+ :ci-config-data="ciConfigData"
+ :ci-file-content="ciFileContent"
+ :commit-sha="commitSha"
+ :is-new-ci-config-file="isNewCiConfigFile"
+ :show-drawer="showDrawer"
+ v-on="$listeners"
+ @open-drawer="openDrawer"
+ @close-drawer="closeDrawer"
+ @set-current-tab="setCurrentTab"
+ @walkthrough-popover-cta-clicked="setScrollToCommitForm"
+ />
+ </div>
+ </div>
<commit-section
v-if="showCommitForm"
:ref="$options.commitSectionRef"
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
index 32e1e18b684..d84fc724d38 100644
--- a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -313,7 +313,7 @@ export default {
errors = [],
warnings = [],
total_warnings: totalWarnings = 0,
- } = err?.response?.data;
+ } = err.response.data;
const [error] = errors;
this.reportError({
diff --git a/app/assets/javascripts/pipeline_new/index.js b/app/assets/javascripts/pipeline_new/index.js
index a645ea8603b..927eeb5e144 100644
--- a/app/assets/javascripts/pipeline_new/index.js
+++ b/app/assets/javascripts/pipeline_new/index.js
@@ -17,7 +17,7 @@ export default () => {
fileParam,
settingsLink,
maxWarnings,
- } = el?.dataset;
+ } = el.dataset;
const variableParams = JSON.parse(varParam);
const fileParams = JSON.parse(fileParam);
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue
index 16fb931ec2b..475dd3bf36e 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag.vue
@@ -238,7 +238,7 @@ export default {
</div>
</template>
<template v-if="dagDocPath" #actions>
- <gl-button :href="dagDocPath" target="__blank" variant="success">
+ <gl-button :href="dagDocPath" target="_blank" variant="confirm">
{{ $options.emptyStateTexts.button }}
</gl-button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js
index 0b59612b25c..85ca52f633e 100644
--- a/app/assets/javascripts/pipelines/components/graph/constants.js
+++ b/app/assets/javascripts/pipelines/components/graph/constants.js
@@ -15,4 +15,8 @@ export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
export const SINGLE_JOB = 'single_job';
export const JOB_DROPDOWN = 'job_dropdown';
+export const BUILD_KIND = 'BUILD';
+export const BRIDGE_KIND = 'BRIDGE';
+
+export const ACTION_FAILURE = 'action_failure';
export const IID_FAILURE = 'missing_iid';
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 015f0519c72..31a34ab4fb5 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -233,6 +233,7 @@ export default {
:view-type="viewType"
@downstreamHovered="setSourceJob"
@pipelineExpandToggle="togglePipelineExpanded"
+ @refreshPipelineGraph="$emit('refreshPipelineGraph')"
@scrollContainer="slidePipelineContainer"
@error="onError"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index 534ad25a35d..f822e2c0874 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -8,7 +8,7 @@ import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import DismissPipelineGraphCallout from '../../graphql/mutations/dismiss_pipeline_notification.graphql';
import getPipelineQuery from '../../graphql/queries/get_pipeline_header_data.query.graphql';
import { reportToSentry, reportMessageToSentry } from '../../utils';
-import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
+import { ACTION_FAILURE, IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
import PipelineGraph from './graph_component.vue';
import GraphViewSelector from './graph_view_selector.vue';
import {
@@ -57,13 +57,29 @@ export default {
showLinks: false,
};
},
- errorTexts: {
- [DRAW_FAILURE]: __('An error occurred while drawing job relationship links.'),
- [IID_FAILURE]: __(
- 'The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data.',
- ),
- [LOAD_FAILURE]: __('We are currently unable to fetch data for this pipeline.'),
- [DEFAULT]: __('An unknown error occurred while loading this graph.'),
+ errors: {
+ [ACTION_FAILURE]: {
+ text: __('An error occurred while performing this action.'),
+ variant: 'danger',
+ },
+ [DRAW_FAILURE]: {
+ text: __('An error occurred while drawing job relationship links.'),
+ variant: 'danger',
+ },
+ [IID_FAILURE]: {
+ text: __(
+ 'The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data.',
+ ),
+ variant: 'info',
+ },
+ [LOAD_FAILURE]: {
+ text: __('Currently unable to fetch data for this pipeline.'),
+ variant: 'danger',
+ },
+ [DEFAULT]: {
+ text: __('An unknown error occurred while loading this graph.'),
+ variant: 'danger',
+ },
},
apollo: {
callouts: {
@@ -154,28 +170,12 @@ export default {
},
computed: {
alert() {
- switch (this.alertType) {
- case DRAW_FAILURE:
- return {
- text: this.$options.errorTexts[DRAW_FAILURE],
- variant: 'danger',
- };
- case IID_FAILURE:
- return {
- text: this.$options.errorTexts[IID_FAILURE],
- variant: 'info',
- };
- case LOAD_FAILURE:
- return {
- text: this.$options.errorTexts[LOAD_FAILURE],
- variant: 'danger',
- };
- default:
- return {
- text: this.$options.errorTexts[DEFAULT],
- variant: 'danger',
- };
- }
+ const { errors } = this.$options;
+
+ return {
+ text: errors[this.alertType]?.text ?? errors[DEFAULT].text,
+ variant: errors[this.alertType]?.variant ?? errors[DEFAULT].variant,
+ };
},
configPaths() {
return {
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index f69b25dfa7c..362571930d6 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { sprintf, __ } from '~/locale';
@@ -7,7 +7,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
-import { SINGLE_JOB } from './constants';
+import { BRIDGE_KIND, SINGLE_JOB } from './constants';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
@@ -35,11 +35,16 @@ import { SINGLE_JOB } from './constants';
*/
export default {
+ i18n: {
+ bridgeBadgeText: __('Trigger job'),
+ unauthorizedTooltip: __('You are not authorized to run this manual job'),
+ },
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
CiIcon,
JobNameComponent,
+ GlBadge,
GlLink,
},
directives: {
@@ -113,6 +118,12 @@ export default {
isSingleItem() {
return this.type === SINGLE_JOB;
},
+ isBridge() {
+ return this.kind === BRIDGE_KIND;
+ },
+ kind() {
+ return this.job?.kind || '';
+ },
nameComponent() {
return this.hasDetails ? 'gl-link' : 'div';
},
@@ -187,6 +198,7 @@ export default {
[this.$options.hoverClass]:
this.relatedDownstreamHovered || this.relatedDownstreamExpanded,
},
+ { 'gl-rounded-lg': this.isBridge },
this.cssClassJobName,
];
},
@@ -213,9 +225,6 @@ export default {
this.$emit('pipelineActionRequestComplete');
},
},
- i18n: {
- unauthorizedTooltip: __('You are not authorized to run this manual job'),
- },
};
</script>
<template>
@@ -253,6 +262,9 @@ export default {
</div>
</div>
</div>
+ <gl-badge v-if="isBridge" class="gl-mt-3" variant="info" size="sm">
+ {{ $options.i18n.bridgeBadgeText }}
+ </gl-badge>
</component>
<action-component
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index d59802196af..9f76d4cec50 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -1,10 +1,22 @@
<script>
-import { GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import {
+ GlBadge,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ GlTooltip,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale';
+import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
+import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { PIPELINE_GRAPHQL_TYPE } from '../../constants';
import { reportToSentry } from '../../utils';
-import { DOWNSTREAM, UPSTREAM } from './constants';
+import { ACTION_FAILURE, DOWNSTREAM, UPSTREAM } from './constants';
export default {
directives: {
@@ -16,7 +28,14 @@ export default {
GlButton,
GlLink,
GlLoadingIcon,
+ GlTooltip,
},
+ styles: {
+ actionSizeClasses: ['gl-h-7 gl-w-7'],
+ flatLeftBorder: ['gl-rounded-bottom-left-none!', 'gl-rounded-top-left-none!'],
+ flatRightBorder: ['gl-rounded-bottom-right-none!', 'gl-rounded-top-right-none!'],
+ },
+ mixins: [glFeatureFlagMixin()],
props: {
columnTitle: {
type: String,
@@ -39,15 +58,44 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ hasActionTooltip: false,
+ isActionLoading: false,
+ };
+ },
computed: {
- buttonBorderClass() {
- return this.isUpstream ? 'gl-border-r-1!' : 'gl-border-l-1!';
+ action() {
+ if (this.glFeatures?.downstreamRetryAction && this.isDownstream) {
+ if (this.isCancelable) {
+ return {
+ icon: 'cancel',
+ method: this.cancelPipeline,
+ ariaLabel: __('Cancel downstream pipeline'),
+ };
+ } else if (this.isRetryable) {
+ return {
+ icon: 'retry',
+ method: this.retryPipeline,
+ ariaLabel: __('Retry downstream pipeline'),
+ };
+ }
+ }
+
+ return {};
+ },
+ buttonBorderClasses() {
+ return this.isUpstream
+ ? ['gl-border-r-0!', ...this.$options.styles.flatRightBorder]
+ : ['gl-border-l-0!', ...this.$options.styles.flatLeftBorder];
},
buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`;
},
- cardSpacingClass() {
- return this.isDownstream ? 'gl-pr-0' : '';
+ cardClasses() {
+ return this.isDownstream
+ ? this.$options.styles.flatRightBorder
+ : this.$options.styles.flatLeftBorder;
},
expandedIcon() {
if (this.isUpstream) {
@@ -64,9 +112,21 @@ export default {
flexDirection() {
return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
},
+ graphqlPipelineId() {
+ return convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, this.pipeline.id);
+ },
+ hasUpdatePipelinePermissions() {
+ return Boolean(this.pipeline?.userPermissions?.updatePipeline);
+ },
+ isCancelable() {
+ return Boolean(this.pipeline?.cancelable && this.hasUpdatePipelinePermissions);
+ },
isDownstream() {
return this.type === DOWNSTREAM;
},
+ isRetryable() {
+ return Boolean(this.pipeline?.retryable && this.hasUpdatePipelinePermissions);
+ },
isSameProject() {
return !this.pipeline.multiproject;
},
@@ -93,13 +153,19 @@ export default {
projectName() {
return this.pipeline.project.name;
},
+ showAction() {
+ return Boolean(this.action?.method && this.action?.icon && this.action?.ariaLabel);
+ },
+ showCardTooltip() {
+ return !this.hasActionTooltip;
+ },
sourceJobName() {
return this.pipeline.sourceJob?.name ?? '';
},
sourceJobInfo() {
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
},
- tooltipText() {
+ cardTooltipText() {
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
${this.sourceJobInfo}`;
},
@@ -108,6 +174,26 @@ export default {
reportToSentry('linked_pipeline', `error: ${err}, info: ${info}`);
},
methods: {
+ cancelPipeline() {
+ this.executePipelineAction(CancelPipelineMutation);
+ },
+ async executePipelineAction(mutation) {
+ try {
+ this.isActionLoading = true;
+
+ await this.$apollo.mutate({
+ mutation,
+ variables: {
+ id: this.graphqlPipelineId,
+ },
+ });
+ this.$emit('refreshPipelineGraph');
+ } catch {
+ this.$emit('error', { type: ACTION_FAILURE });
+ } finally {
+ this.isActionLoading = false;
+ }
+ },
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
@@ -122,6 +208,12 @@ export default {
onDownstreamHoverLeave() {
this.$emit('downstreamHovered', '');
},
+ retryPipeline() {
+ this.executePipelineAction(RetryPipelineMutation);
+ },
+ setActionTooltip(flag) {
+ this.hasActionTooltip = flag;
+ },
},
};
</script>
@@ -129,33 +221,48 @@ export default {
<template>
<div
ref="linkedPipeline"
- v-gl-tooltip
- class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
+ class="gl-h-full gl-display-flex!"
:class="flexDirection"
- :title="tooltipText"
data-qa-selector="linked_pipeline_container"
@mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave"
>
- <div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass">
- <div class="gl-display-flex gl-pr-3">
- <ci-status
- v-if="!pipelineIsLoading"
- :status="pipelineStatus"
- :size="24"
- css-classes="gl-top-0 gl-pr-2"
- />
+ <gl-tooltip v-if="showCardTooltip" :target="() => $refs.linkedPipeline">
+ {{ cardTooltipText }}
+ </gl-tooltip>
+ <div class="gl-bg-white gl-border gl-p-3 gl-rounded-lg gl-w-full" :class="cardClasses">
+ <div class="gl-display-flex gl-gap-x-3">
+ <ci-status v-if="!pipelineIsLoading" :status="pipelineStatus" :size="24" css-classes="" />
<div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div>
- <div class="gl-display-flex gl-flex-direction-column gl-downstream-pipeline-job-width">
+ <div
+ class="gl-display-flex gl-downstream-pipeline-job-width gl-flex-direction-column gl-line-height-normal"
+ >
<span class="gl-text-truncate" data-testid="downstream-title">
{{ downstreamTitle }}
</span>
<div class="gl-text-truncate">
- <gl-link class="gl-text-blue-500!" :href="pipeline.path" data-testid="pipelineLink"
+ <gl-link
+ class="gl-text-blue-500! gl-font-sm"
+ :href="pipeline.path"
+ data-testid="pipelineLink"
>#{{ pipeline.id }}</gl-link
>
</div>
</div>
+ <gl-button
+ v-if="showAction"
+ v-gl-tooltip
+ :title="action.ariaLabel"
+ :loading="isActionLoading"
+ :icon="action.icon"
+ class="gl-rounded-full!"
+ :class="$options.styles.actionSizeClasses"
+ :aria-label="action.ariaLabel"
+ @click="action.method"
+ @mouseover="setActionTooltip(true)"
+ @mouseout="setActionTooltip(false)"
+ />
+ <div v-else :class="$options.styles.actionSizeClasses"></div>
</div>
<div class="gl-pt-2">
<gl-badge size="sm" variant="info" data-testid="downstream-pipeline-label">
@@ -166,8 +273,8 @@ export default {
<div class="gl-display-flex">
<gl-button
:id="buttonId"
- class="gl-shadow-none! gl-rounded-0!"
- :class="`js-pipeline-expand-${pipeline.id} ${buttonBorderClass}`"
+ class="gl-border! gl-shadow-none! gl-rounded-lg!"
+ :class="[`js-pipeline-expand-${pipeline.id}`, buttonBorderClasses]"
:icon="expandedIcon"
:aria-label="__('Expand pipeline')"
data-testid="expand-pipeline-button"
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index 3c1208afbf0..b06c2f15042 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -66,14 +66,13 @@ export default {
columnClass() {
const positionValues = {
right: 'gl-ml-6',
- left: 'gl-mr-6',
+ left: 'gl-mx-6',
};
+
return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
},
computedTitleClasses() {
- const positionalClasses = this.isUpstream
- ? ['gl-w-full', 'gl-text-right', 'gl-linked-pipeline-padding']
- : [];
+ const positionalClasses = this.isUpstream ? ['gl-w-full', 'gl-linked-pipeline-padding'] : [];
return [...this.$options.titleClasses, ...positionalClasses];
},
@@ -202,7 +201,7 @@ export default {
<li
v-for="pipeline in linkedPipelines"
:key="pipeline.id"
- class="gl-display-flex gl-mb-4"
+ class="gl-display-flex gl-mb-3"
:class="{ 'gl-flex-direction-row-reverse': isUpstream }"
>
<linked-pipeline
@@ -215,6 +214,7 @@ export default {
@downstreamHovered="onDownstreamHovered"
@pipelineClicked="onPipelineClick(pipeline)"
@pipelineExpandToggle="onPipelineExpandToggle"
+ @refreshPipelineGraph="$emit('refreshPipelineGraph')"
/>
<div
v-if="showContainer(pipeline.id)"
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 04b78b8aa23..37878f3fb6d 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -93,6 +93,7 @@ export default {
data() {
return {
pipeline: null,
+ failureMessages: [],
failureType: null,
isCanceling: false,
isRetrying: false,
@@ -159,8 +160,9 @@ export default {
},
},
methods: {
- reportFailure(errorType) {
+ reportFailure(errorType, errorMessages = []) {
this.failureType = errorType;
+ this.failureMessages = errorMessages;
},
async postPipelineAction(name, mutation) {
try {
@@ -176,7 +178,7 @@ export default {
if (errors.length > 0) {
this.isRetrying = false;
- this.reportFailure(POST_FAILURE);
+ this.reportFailure(POST_FAILURE, errors);
} else {
await this.$apollo.queries.pipeline.refetch();
if (!this.isFinished) {
@@ -214,7 +216,7 @@ export default {
});
if (errors.length > 0) {
- this.reportFailure(DELETE_FAILURE);
+ this.reportFailure(DELETE_FAILURE, errors);
this.isDeleting = false;
} else {
redirectTo(setUrlFragment(this.paths.pipelinesPath, 'delete_success'));
@@ -231,9 +233,11 @@ export default {
</script>
<template>
<div class="js-pipeline-header-container">
- <gl-alert v-if="hasError" :variant="failure.variant" :dismissible="false">{{
- failure.text
- }}</gl-alert>
+ <gl-alert v-if="hasError" :title="failure.text" :variant="failure.variant" :dismissible="false">
+ <div v-for="(failureMessage, index) in failureMessages" :key="`failure-message-${index}`">
+ {{ failureMessage }}
+ </div>
+ </gl-alert>
<ci-header
v-if="shouldRenderContent"
:status="pipeline.detailedStatus"
@@ -261,6 +265,7 @@ export default {
v-if="canCancelPipeline"
:loading="isCanceling"
:disabled="isCanceling"
+ class="gl-ml-3"
variant="danger"
data-testid="cancelPipeline"
@click="cancelPipeline()"
diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue
new file mode 100644
index 00000000000..9e886fd7a48
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_app.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import GetFailedJobsQuery from '../../graphql/queries/get_failed_jobs.query.graphql';
+import { prepareFailedJobs } from './utils';
+import FailedJobsTable from './failed_jobs_table.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ FailedJobsTable,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ pipelineIid: {
+ default: '',
+ },
+ },
+ props: {
+ failedJobsSummary: {
+ type: Array,
+ required: true,
+ },
+ },
+ apollo: {
+ failedJobs: {
+ query: GetFailedJobsQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ pipelineIid: this.pipelineIid,
+ };
+ },
+ update({ project }) {
+ if (project?.pipeline?.jobs?.nodes) {
+ return project.pipeline.jobs.nodes.map((job) => {
+ return { normalizedId: getIdFromGraphQLId(job.id), ...job };
+ });
+ }
+ return [];
+ },
+ result() {
+ this.preparedFailedJobs = prepareFailedJobs(this.failedJobs, this.failedJobsSummary);
+ },
+ error() {
+ createFlash({ message: s__('Jobs|There was a problem fetching the failed jobs.') });
+ },
+ },
+ },
+ data() {
+ return {
+ failedJobs: [],
+ preparedFailedJobs: [],
+ };
+ },
+ computed: {
+ loading() {
+ return this.$apollo.queries.failedJobs.loading;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="loading" size="lg" class="gl-mt-4" />
+ <failed-jobs-table v-else :failed-jobs="preparedFailedJobs" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
new file mode 100644
index 00000000000..1c646bdf3d6
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlButton, GlLink, GlSafeHtmlDirective, GlTableLite } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
+import { redirectTo } from '~/lib/utils/url_utility';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import RetryFailedJobMutation from '../../graphql/mutations/retry_failed_job.mutation.graphql';
+import { DEFAULT_FIELDS } from '../../constants';
+
+export default {
+ fields: DEFAULT_FIELDS,
+ retry: __('Retry'),
+ components: {
+ CiBadge,
+ GlButton,
+ GlLink,
+ GlTableLite,
+ },
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
+ props: {
+ failedJobs: {
+ type: Array,
+ required: true,
+ },
+ },
+ methods: {
+ async retryJob(id) {
+ try {
+ const {
+ data: {
+ jobRetry: { errors, job },
+ },
+ } = await this.$apollo.mutate({
+ mutation: RetryFailedJobMutation,
+ variables: { id },
+ });
+ if (errors.length > 0) {
+ this.showErrorMessage();
+ } else {
+ redirectTo(job.detailedStatus.detailsPath);
+ }
+ } catch {
+ this.showErrorMessage();
+ }
+ },
+ canRetryJob(job) {
+ return job.retryable && job.userPermissions.updateBuild;
+ },
+ showErrorMessage() {
+ createFlash({ message: s__('Job|There was a problem retrying the failed job.') });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-table-lite :items="failedJobs" :fields="$options.fields" stacked="lg" fixed>
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(name)="{ item }">
+ <div
+ class="gl-display-flex gl-align-items-center gl-lg-justify-content-start gl-justify-content-end"
+ >
+ <ci-badge :status="item.detailedStatus" :show-text="false" class="gl-mr-3" />
+ <div class="gl-text-truncate">
+ <gl-link
+ :href="item.detailedStatus.detailsPath"
+ class="gl-font-weight-bold gl-text-gray-900!"
+ >
+ {{ item.name }}
+ </gl-link>
+ </div>
+ </div>
+ </template>
+
+ <template #cell(stage)="{ item }">
+ <div class="gl-text-truncate">
+ <span>{{ item.stage.name }}</span>
+ </div>
+ </template>
+
+ <template #cell(failure)="{ item }">
+ <span>{{ item.failure }}</span>
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <gl-button
+ v-if="canRetryJob(item)"
+ icon="repeat"
+ :title="$options.retry"
+ :aria-label="$options.retry"
+ @click="retryJob(item.id)"
+ />
+ </template>
+
+ <template #row-details="{ item }">
+ <pre
+ v-if="item.userPermissions.readBuild"
+ class="gl-w-full gl-text-left gl-border-none"
+ data-testid="job-log"
+ >
+ <code v-safe-html="item.failureSummary" class="gl-reset-bg gl-p-0" >
+ </code>
+ </pre>
+ </template>
+ </gl-table-lite>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/jobs/utils.js b/app/assets/javascripts/pipelines/components/jobs/utils.js
new file mode 100644
index 00000000000..c8414d44d14
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/jobs/utils.js
@@ -0,0 +1,33 @@
+/*
+ We get the failure and failure summary from Rails which has
+ a summary failure log. Here we combine that data with the data
+ from GraphQL to display the log.
+
+ failedJobs is from GraphQL
+ failedJobsSummary is from Rails
+ */
+
+export const prepareFailedJobs = (failedJobs = [], failedJobsSummary = []) => {
+ const combinedJobs = [];
+
+ if (failedJobs.length > 0 && failedJobsSummary.length > 0) {
+ failedJobs.forEach((failedJob) => {
+ const foundJob = failedJobsSummary.find(
+ (failedJobSummary) => failedJob.normalizedId === failedJobSummary.id,
+ );
+
+ if (foundJob) {
+ combinedJobs.push({
+ ...failedJob,
+ failure: foundJob?.failure,
+ failureSummary: foundJob?.failure_summary,
+ // this field is needed for the slot row-details
+ // on the failed_jobs_table.vue component
+ _showDetails: true,
+ });
+ }
+ });
+ }
+
+ return combinedJobs;
+};
diff --git a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
index 62c785d7ad2..66d30c10362 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
@@ -1,6 +1,7 @@
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
import { __ } from '~/locale';
+import { failedJobsTabName, jobsTabName, needsTabName, testReportTabName } from '../constants';
import PipelineGraphWrapper from './graph/graph_component_wrapper.vue';
import Dag from './dag/dag.vue';
import JobsApp from './jobs/jobs_app.vue';
@@ -16,6 +17,12 @@ export default {
testsTitle: __('Tests'),
},
},
+ tabNames: {
+ needs: needsTabName,
+ jobs: jobsTabName,
+ failures: failedJobsTabName,
+ tests: testReportTabName,
+ },
components: {
Dag,
GlTab,
@@ -25,24 +32,47 @@ export default {
PipelineGraphWrapper,
TestReports,
},
+ inject: ['defaultTabValue'],
+ methods: {
+ isActive(tabName) {
+ return tabName === this.defaultTabValue;
+ },
+ },
};
</script>
<template>
<gl-tabs>
- <gl-tab :title="$options.i18n.tabs.pipelineTitle" data-testid="pipeline-tab">
+ <gl-tab ref="pipelineTab" :title="$options.i18n.tabs.pipelineTitle" data-testid="pipeline-tab">
<pipeline-graph-wrapper />
</gl-tab>
- <gl-tab :title="$options.i18n.tabs.needsTitle" data-testid="dag-tab">
+ <gl-tab
+ ref="dagTab"
+ :title="$options.i18n.tabs.needsTitle"
+ :active="isActive($options.tabNames.needs)"
+ data-testid="dag-tab"
+ >
<dag />
</gl-tab>
- <gl-tab :title="$options.i18n.tabs.jobsTitle" data-testid="jobs-tab">
+ <gl-tab
+ :title="$options.i18n.tabs.jobsTitle"
+ :active="isActive($options.tabNames.jobs)"
+ data-testid="jobs-tab"
+ >
<jobs-app />
</gl-tab>
- <gl-tab :title="$options.i18n.tabs.failedJobsTitle" data-testid="failed-jobs-tab">
+ <gl-tab
+ :title="$options.i18n.tabs.failedJobsTitle"
+ :active="isActive($options.tabNames.failures)"
+ data-testid="failed-jobs-tab"
+ >
<failed-jobs-app />
</gl-tab>
- <gl-tab :title="$options.i18n.tabs.testsTitle" data-testid="tests-tab">
+ <gl-tab
+ :title="$options.i18n.tabs.testsTitle"
+ :active="isActive($options.tabNames.tests)"
+ data-testid="tests-tab"
+ >
<test-reports />
</gl-tab>
<slot></slot>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 5a9c85a0f10..3bbdfc73e1b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -1,7 +1,9 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
+import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import PipelinesCiTemplates from './empty_state/pipelines_ci_templates.vue';
+import IosTemplates from './empty_state/ios_templates.vue';
export default {
i18n: {
@@ -10,7 +12,9 @@ export default {
name: 'PipelinesEmptyState',
components: {
GlEmptyState,
+ GitlabExperiment,
PipelinesCiTemplates,
+ IosTemplates,
},
props: {
emptyStateSvgPath: {
@@ -21,26 +25,24 @@ export default {
type: Boolean,
required: true,
},
- ciRunnerSettingsPath: {
+ registrationToken: {
type: String,
required: false,
default: null,
},
- anyRunnersAvailable: {
- type: Boolean,
- required: false,
- default: true,
- },
},
};
</script>
<template>
<div>
- <pipelines-ci-templates
- v-if="canSetCi"
- :ci-runner-settings-path="ciRunnerSettingsPath"
- :any-runners-available="anyRunnersAvailable"
- />
+ <gitlab-experiment v-if="canSetCi" name="ios_specific_templates">
+ <template #control>
+ <pipelines-ci-templates />
+ </template>
+ <template #candidate>
+ <ios-templates :registration-token="registrationToken" />
+ </template>
+ </gitlab-experiment>
<gl-empty-state
v-else
title=""
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue
index 3b312e78d11..64d4414eb94 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue
@@ -12,15 +12,31 @@ export default {
},
mixins: [Tracking.mixin()],
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
+ props: {
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ filterTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
data() {
- const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
- return {
- name,
- logo,
- link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
- description: sprintf(this.$options.i18n.description, { name }),
- };
- });
+ const templates = this.suggestedCiTemplates
+ .filter(
+ (template) => !this.filterTemplates.length || this.filterTemplates.includes(template.name),
+ )
+ .map(({ name, logo, title }) => {
+ return {
+ name: title || name,
+ logo,
+ link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
+ description: sprintf(this.$options.i18n.description, { name: title || name }),
+ };
+ });
return {
templates,
@@ -34,7 +50,9 @@ export default {
},
},
i18n: {
- description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
+ description: s__(
+ 'Pipelines|Continuous integration and deployment template to test and deploy your %{name} project.',
+ ),
cta: s__('Pipelines|Use template'),
},
AVATAR_SHAPE_OPTION_RECT,
@@ -67,6 +85,7 @@ export default {
</div>
</div>
<gl-button
+ :disabled="disabled"
category="primary"
variant="confirm"
:href="template.link"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue
new file mode 100644
index 00000000000..8ff311e90e7
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue
@@ -0,0 +1,220 @@
+<script>
+import { GlButton, GlCard, GlSprintf, GlLink, GlPopover, GlModalDirective } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
+import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+import apolloProvider from '~/pipelines/graphql/provider';
+import CiTemplates from './ci_templates.vue';
+
+export default {
+ components: {
+ GlButton,
+ GlCard,
+ GlSprintf,
+ GlLink,
+ GlPopover,
+ RunnerInstructionsModal,
+ CiTemplates,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['pipelineEditorPath', 'iosRunnersAvailable'],
+ props: {
+ registrationToken: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ apolloProvider,
+ iOSTemplateName: 'iOS-Fastlane',
+ modalId: 'runner-instructions-modal',
+ runnerDocsLink: 'https://docs.gitlab.com/runner/install/osx',
+ whatElseLink: helpPagePath('ci/index.md'),
+ i18n: {
+ title: s__('Pipelines|Get started with GitLab CI/CD'),
+ subtitle: s__('Pipelines|Building for iOS?'),
+ explanation: s__("Pipelines|We'll walk you through how to deploy to iOS in two easy steps."),
+ runnerSetupTitle: s__('Pipelines|1. Set up a runner'),
+ runnerSetupButton: s__('Pipelines|Set up a runner'),
+ runnerSetupBodyUnfinished: s__(
+ 'Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline.',
+ ),
+ runnerSetupBodyFinished: s__(
+ 'Pipelines|You have runners available to run your job now. No need to do anything else.',
+ ),
+ runnerSetupPopoverTitle: s__(
+ "Pipelines|Let's get that runner set up! %{emojiStart}tada%{emojiEnd}",
+ ),
+ runnerSetupPopoverBodyLine1: s__(
+ 'Pipelines|Follow these instructions to install GitLab Runner on macOS.',
+ ),
+ runnerSetupPopoverBodyLine2: s__(
+ 'Pipelines|Need more information to set up your runner? %{linkStart}Check out our documentation%{linkEnd}.',
+ ),
+ configurePipelineTitle: s__('Pipelines|2. Configure deployment pipeline'),
+ configurePipelineBody: s__("Pipelines|We'll guide you through a simple pipeline set-up."),
+ configurePipelineButton: s__('Pipelines|Configure pipeline'),
+ noWalkthroughTitle: s__("Pipelines|Don't need a guide? Jump in right away with a template."),
+ noWalkthroughExplanation: s__('Pipelines|Based on your project, we recommend this template:'),
+ notBuildingForIos: s__(
+ "Pipelines|Not building for iOS or not what you're looking for? %{linkStart}See what else%{linkEnd} GitLab CI/CD has to offer.",
+ ),
+ },
+ data() {
+ return {
+ isModalShown: false,
+ isPopoverShown: false,
+ isRunnerSetupFinished: this.iosRunnersAvailable,
+ popoverTarget: `${this.$options.modalId}___BV_modal_content_`,
+ configurePipelineLink: mergeUrlParams(
+ { template: this.$options.iOSTemplateName },
+ this.pipelineEditorPath,
+ ),
+ };
+ },
+ computed: {
+ runnerSetupBodyText() {
+ return this.iosRunnersAvailable
+ ? this.$options.i18n.runnerSetupBodyFinished
+ : this.$options.i18n.runnerSetupBodyUnfinished;
+ },
+ },
+ methods: {
+ showModal() {
+ this.isModalShown = true;
+ },
+ hideModal() {
+ this.togglePopover();
+ this.isRunnerSetupFinished = true;
+ },
+ togglePopover() {
+ this.isPopoverShown = !this.isPopoverShown;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.title }}</h2>
+ <h3 class="gl-font-lg gl-text-gray-900 gl-mt-1">{{ $options.i18n.subtitle }}</h3>
+ <p>{{ $options.i18n.explanation }}</p>
+
+ <div class="gl-lg-display-flex">
+ <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4">
+ <gl-card body-class="gl-display-flex gl-flex-grow-1">
+ <div
+ class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start"
+ >
+ <div>
+ <div class="gl-py-5">
+ <gl-emoji
+ v-show="isRunnerSetupFinished"
+ class="gl-font-size-h2-xl"
+ data-name="white_check_mark"
+ data-testid="runner-setup-marked-completed"
+ />
+ <gl-emoji
+ v-show="!isRunnerSetupFinished"
+ class="gl-font-size-h2-xl"
+ data-name="tools"
+ data-testid="runner-setup-marked-todo"
+ />
+ </div>
+ <span class="gl-text-gray-800 gl-font-weight-bold">
+ {{ $options.i18n.runnerSetupTitle }}
+ </span>
+ <p class="gl-font-sm gl-mt-3">{{ runnerSetupBodyText }}</p>
+ </div>
+
+ <gl-button
+ v-if="!iosRunnersAvailable"
+ v-gl-modal-directive="$options.modalId"
+ category="primary"
+ variant="confirm"
+ @click="showModal"
+ >
+ {{ $options.i18n.runnerSetupButton }}
+ </gl-button>
+ <runner-instructions-modal
+ v-if="isModalShown"
+ :modal-id="$options.modalId"
+ :registration-token="registrationToken"
+ default-platform-name="osx"
+ @shown="togglePopover"
+ @hide="hideModal"
+ />
+ <gl-popover
+ v-if="isPopoverShown"
+ :show="true"
+ :show-close-button="true"
+ :target="popoverTarget"
+ triggers="manual"
+ placement="left"
+ fallback-placement="clockwise"
+ >
+ <template #title>
+ <gl-sprintf :message="$options.i18n.runnerSetupPopoverTitle">
+ <template #emoji="{ content }">
+ <gl-emoji class="gl-ml-2" :data-name="content" />
+ </template>
+ </gl-sprintf>
+ </template>
+ <div class="gl-mb-5">
+ {{ $options.i18n.runnerSetupPopoverBodyLine1 }}
+ </div>
+ <gl-sprintf :message="$options.i18n.runnerSetupPopoverBodyLine2">
+ <template #link="{ content }">
+ <gl-link :href="$options.runnerDocsLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-popover>
+ </div>
+ </gl-card>
+ </div>
+ <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4">
+ <gl-card body-class="gl-display-flex gl-flex-grow-1">
+ <div
+ class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start"
+ >
+ <div>
+ <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="tools" /></div>
+ <span class="gl-text-gray-800 gl-font-weight-bold">
+ {{ $options.i18n.configurePipelineTitle }}
+ </span>
+ <p class="gl-font-sm gl-mt-3">{{ $options.i18n.configurePipelineBody }}</p>
+ </div>
+
+ <gl-button
+ :disabled="!isRunnerSetupFinished"
+ category="primary"
+ variant="confirm"
+ data-testid="configure-pipeline-link"
+ :href="configurePipelineLink"
+ >
+ {{ $options.i18n.configurePipelineButton }}
+ </gl-button>
+ </div>
+ </gl-card>
+ </div>
+ </div>
+ <h3 class="gl-font-lg gl-text-gray-900 gl-mt-5">{{ $options.i18n.noWalkthroughTitle }}</h3>
+ <p>{{ $options.i18n.noWalkthroughExplanation }}</p>
+ <ci-templates
+ :filter-templates="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ $options.iOSTemplateName,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :disabled="!isRunnerSetupFinished"
+ />
+ <p>
+ <gl-sprintf :message="$options.i18n.notBuildingForIos">
+ <template #link="{ content }">
+ <gl-link :href="$options.whatElseLink">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
index be46a7f5cec..3eafb36bd1d 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
@@ -33,19 +33,7 @@ export default {
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
- inject: ['pipelineEditorPath'],
- props: {
- ciRunnerSettingsPath: {
- type: String,
- required: false,
- default: null,
- },
- anyRunnersAvailable: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
+ inject: ['anyRunnersAvailable', 'pipelineEditorPath', 'ciRunnerSettingsPath'],
data() {
return {
gettingStartedTemplateUrl: mergeUrlParams(
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
index 2b33467e948..e35fccf2d7e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
@@ -36,7 +36,7 @@ export default {
};
</script>
<template>
- <div data-testid="pipeline-mini-graph">
+ <div data-testid="pipeline-mini-graph" class="gl-display-inline gl-vertical-align-middle gl-my-1">
<div
v-for="stage in stages"
:key="stage.name"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
index afcb04cd7eb..53e21d4ce8b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
@@ -12,7 +12,8 @@
* 4. Commit widget
*/
-import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { GlDropdown, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __, sprintf } from '~/locale';
@@ -21,7 +22,7 @@ import JobItem from './job_item.vue';
export default {
components: {
- GlIcon,
+ CiIcon,
GlLoadingIcon,
GlDropdown,
JobItem,
@@ -51,14 +52,6 @@ export default {
dropdownContent: [],
};
},
- computed: {
- triggerButtonClass() {
- return `ci-status-icon-${this.stage.status.group}`;
- },
- borderlessIcon() {
- return `${this.stage.status.icon}_borderless`;
- },
- },
watch: {
updateDropdown() {
if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
@@ -114,15 +107,21 @@ export default {
variant="link"
:aria-label="stageAriaLabel(stage.title)"
:lazy="true"
- :popper-opts="{ placement: 'bottom' }"
- :toggle-class="['mini-pipeline-graph-dropdown-toggle', triggerButtonClass]"
+ :popper-opts="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ placement: 'bottom',
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :toggle-class="['gl-rounded-full!']"
menu-class="mini-pipeline-graph-dropdown-menu"
@show="onShowDropdown"
>
<template #button-content>
- <span class="gl-pointer-events-none">
- <gl-icon :name="borderlessIcon" />
- </span>
+ <ci-icon
+ is-interactive
+ css-classes="gl-rounded-full"
+ :size="24"
+ :status="stage.status"
+ class="gl-align-items-center gl-display-inline-flex"
+ />
</template>
<gl-loading-icon v-if="isLoading" size="sm" />
<ul
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index db9dc74863d..485e338f639 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -107,16 +107,11 @@ export default {
type: Object,
required: true,
},
- ciRunnerSettingsPath: {
+ registrationToken: {
type: String,
required: false,
default: null,
},
- anyRunnersAvailable: {
- type: Boolean,
- required: false,
- default: true,
- },
},
data() {
return {
@@ -386,8 +381,7 @@ export default {
v-else-if="stateToRender === $options.stateMap.emptyState"
:empty-state-svg-path="emptyStateSvgPath"
:can-set-ci="canCreatePipeline"
- :ci-runner-settings-path="ciRunnerSettingsPath"
- :any-runners-available="anyRunnersAvailable"
+ :registration-token="registrationToken"
/>
<gl-empty-state
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index 77b9c2b5203..53da98434b0 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -174,12 +174,13 @@ export default {
<div></div>
<linked-pipelines-mini-list
v-if="item.triggered_by"
- :triggered-by="[item.triggered_by]"
+ :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ item.triggered_by,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
data-testid="mini-graph-upstream"
/>
<pipeline-mini-graph
v-if="item.details && item.details.stages && item.details.stages.length > 0"
- class="gl-display-inline"
:stages="item.details.stages"
:update-dropdown="updateGraphDropdown"
@pipelineActionRequestComplete="onPipelineActionRequestComplete"
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
index 51373e712ff..9b0e6560c53 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -35,7 +35,7 @@ export default {
},
computed: {
...mapState(['pageInfo']),
- ...mapGetters(['getSuiteTests', 'getSuiteTestCount']),
+ ...mapGetters(['getSuiteTests', 'getSuiteTestCount', 'getSuiteArtifactsExpired']),
hasSuites() {
return this.getSuiteTests.length > 0;
},
@@ -80,7 +80,8 @@ export default {
<div
v-for="(testCase, index) in getSuiteTests"
:key="index"
- class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
+ class="gl-responsive-table-row rounded align-items-md-start"
+ data-testid="test-case-row"
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div>
@@ -157,7 +158,16 @@ export default {
</div>
<div v-else>
- <p class="js-no-test-cases">{{ s__('TestReports|There are no test cases to display.') }}</p>
+ <p data-testid="no-test-cases">
+ {{ s__('TestReports|There are no test cases to display.') }}
+ </p>
+ <p v-if="getSuiteArtifactsExpired" data-testid="artifacts-expired">
+ {{
+ s__(
+ 'TestReports|Test details are populated by job artifacts. The job artifacts from this pipeline are expired.',
+ )
+ }}
+ </p>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 36f708ff2af..0510992e962 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -9,6 +9,7 @@ export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule';
export const NEEDS_PROPERTY = 'needs';
export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds';
+export const PIPELINE_GRAPHQL_TYPE = 'Ci::Pipeline';
export const ICONS = {
TAG: 'tag',
@@ -44,6 +45,28 @@ export const UNSUPPORTED_DATA = 'unsupported_data';
export const CHILD_VIEW = 'child';
+// Pipeline tabs
+
+export const TAB_QUERY_PARAM = 'tab';
+
+export const needsTabName = 'dag';
+export const jobsTabName = 'builds';
+export const failedJobsTabName = 'failures';
+export const testReportTabName = 'test_report';
+export const securityTabName = 'security';
+export const licensesTabName = 'licenses';
+export const codeQualityTabName = 'codequality_report';
+
+export const validPipelineTabNames = [
+ needsTabName,
+ jobsTabName,
+ failedJobsTabName,
+ testReportTabName,
+ securityTabName,
+ licensesTabName,
+ codeQualityTabName,
+];
+
// Constants for the ID and IID selection dropdown
export const PipelineKeyOptions = [
{
@@ -62,3 +85,27 @@ export const TOAST_MESSAGE = s__('Pipeline|Creating pipeline.');
export const BUTTON_TOOLTIP_RETRY = __('Retry failed jobs');
export const BUTTON_TOOLTIP_CANCEL = __('Cancel');
+
+export const DEFAULT_FIELDS = [
+ {
+ key: 'name',
+ label: __('Name'),
+ columnClass: 'gl-w-20p',
+ },
+ {
+ key: 'stage',
+ label: __('Stage'),
+ columnClass: 'gl-w-20p',
+ },
+ {
+ key: 'failure',
+ label: __('Failure'),
+ columnClass: 'gl-w-40p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'gl-text-right',
+ columnClass: 'gl-w-20p',
+ },
+];
diff --git a/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql b/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql
new file mode 100644
index 00000000000..1955cc9b0ac
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/mutations/retry_failed_job.mutation.graphql
@@ -0,0 +1,12 @@
+mutation retryFailedJob($id: CiBuildID!) {
+ jobRetry(input: { id: $id }) {
+ job {
+ id
+ detailedStatus {
+ id
+ detailsPath
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/pipelines/graphql/provider.js b/app/assets/javascripts/pipelines/graphql/provider.js
new file mode 100644
index 00000000000..ef96b443da8
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/provider.js
@@ -0,0 +1,9 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
+Vue.use(VueApollo);
+
+export default new VueApollo({
+ defaultClient: createDefaultClient(),
+});
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
new file mode 100644
index 00000000000..14e9a838f4b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
@@ -0,0 +1,41 @@
+query getFailedJobs($fullPath: ID!, $pipelineIid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $pipelineIid) {
+ id
+ jobs(statuses: FAILED) {
+ nodes {
+ status
+ detailedStatus {
+ id
+ detailsPath
+ group
+ icon
+ label
+ text
+ tooltip
+ action {
+ id
+ buttonTitle
+ icon
+ method
+ path
+ title
+ }
+ }
+ id
+ stage {
+ id
+ name
+ }
+ name
+ retryable
+ userPermissions {
+ readBuild
+ updateBuild
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 338de65e795..fd869014570 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -1,10 +1,11 @@
import createFlash from '~/flash';
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
import createDagApp from './pipeline_details_dag';
import { createPipelinesDetailApp } from './pipeline_details_graph';
import { createPipelineHeaderApp } from './pipeline_details_header';
import { createPipelineNotificationApp } from './pipeline_details_notification';
import { createPipelineJobsApp } from './pipeline_details_jobs';
+import { createPipelineFailedJobsApp } from './pipeline_details_failed_jobs';
import { apolloProvider } from './pipeline_shared_client';
import { createTestDetails } from './pipeline_test_details';
@@ -16,6 +17,7 @@ const SELECTORS = {
PIPELINE_TABS: '#js-pipeline-tabs',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
+ PIPELINE_FAILED_JOBS: '#js-pipeline-failed-jobs-vue',
};
export default async function initPipelineDetailsBundle() {
@@ -79,5 +81,15 @@ export default async function initPipelineDetailsBundle() {
message: __('An error occurred while loading the Jobs tab.'),
});
}
+
+ if (gon.features?.failedJobsTabVue) {
+ try {
+ createPipelineFailedJobsApp(SELECTORS.PIPELINE_FAILED_JOBS);
+ } catch {
+ createFlash({
+ message: s__('Jobs|An error occurred while loading the Failed Jobs tab.'),
+ });
+ }
+ }
}
}
diff --git a/app/assets/javascripts/pipelines/pipeline_details_dag.js b/app/assets/javascripts/pipelines/pipeline_details_dag.js
index e2835ecc4d1..b2cb0457c4d 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_dag.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_dag.js
@@ -17,7 +17,7 @@ const createDagApp = (apolloProvider) => {
emptySvgPath,
pipelineProjectPath,
pipelineIid,
- } = el?.dataset;
+ } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
diff --git a/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js b/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js
new file mode 100644
index 00000000000..7bf3b64bf47
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipeline_details_failed_jobs.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import FailedJobsApp from './components/jobs/failed_jobs_app.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export const createPipelineFailedJobsApp = (selector) => {
+ const containerEl = document.querySelector(selector);
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath, pipelineIid, failedJobsSummaryData } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ apolloProvider,
+ provide: {
+ fullPath,
+ pipelineIid,
+ },
+ render(createElement) {
+ return createElement(FailedJobsApp, {
+ props: {
+ failedJobsSummary: JSON.parse(failedJobsSummaryData),
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_header.js b/app/assets/javascripts/pipelines/pipeline_details_header.js
index 1c619768764..2fedd7e7a98 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_header.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_header.js
@@ -11,7 +11,7 @@ export const createPipelineHeaderApp = (elSelector, apolloProvider, graphqlResou
return;
}
- const { fullPath, pipelineId, pipelineIid, pipelinesPath } = el?.dataset;
+ const { fullPath, pipelineId, pipelineIid, pipelinesPath } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
diff --git a/app/assets/javascripts/pipelines/pipeline_details_notification.js b/app/assets/javascripts/pipelines/pipeline_details_notification.js
index 0061be843c5..b480fc7c713 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_notification.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_notification.js
@@ -11,7 +11,7 @@ export const createPipelineNotificationApp = (elSelector, apolloProvider) => {
return;
}
- const { deprecatedKeywordsDocPath, fullPath, pipelineIid } = el?.dataset;
+ const { deprecatedKeywordsDocPath, fullPath, pipelineIid } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
diff --git a/app/assets/javascripts/pipelines/pipeline_tabs.js b/app/assets/javascripts/pipelines/pipeline_tabs.js
index ff88c6215e5..530917f0402 100644
--- a/app/assets/javascripts/pipelines/pipeline_tabs.js
+++ b/app/assets/javascripts/pipelines/pipeline_tabs.js
@@ -1,7 +1,10 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import PipelineTabs from 'ee_else_ce/pipelines/components/pipeline_tabs.vue';
-import { reportToSentry } from './utils';
+import { removeParams, updateHistory } from '~/lib/utils/url_utility';
+import { TAB_QUERY_PARAM } from '~/pipelines/constants';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { getPipelineDefaultTab, reportToSentry } from './utils';
Vue.use(VueApollo);
@@ -17,7 +20,19 @@ const createPipelineTabs = (selector, apolloProvider) => {
downloadablePathForReportType,
exposeSecurityDashboard,
exposeLicenseScanningData,
+ graphqlResourceEtag,
+ pipelineIid,
+ pipelineProjectPath,
} = dataset;
+
+ const defaultTabValue = getPipelineDefaultTab(window.location.href);
+
+ updateHistory({
+ url: removeParams([TAB_QUERY_PARAM]),
+ title: document.title,
+ replace: true,
+ });
+
// eslint-disable-next-line no-new
new Vue({
el: selector,
@@ -26,11 +41,15 @@ const createPipelineTabs = (selector, apolloProvider) => {
},
apolloProvider,
provide: {
- canGenerateCodequalityReports: JSON.parse(canGenerateCodequalityReports),
+ canGenerateCodequalityReports: parseBoolean(canGenerateCodequalityReports),
codequalityReportDownloadPath,
+ defaultTabValue,
downloadablePathForReportType,
- exposeSecurityDashboard: JSON.parse(exposeSecurityDashboard),
- exposeLicenseScanningData: JSON.parse(exposeLicenseScanningData),
+ exposeSecurityDashboard: parseBoolean(exposeSecurityDashboard),
+ exposeLicenseScanningData: parseBoolean(exposeLicenseScanningData),
+ graphqlResourceEtag,
+ pipelineIid,
+ pipelineProjectPath,
},
errorCaptured(err, _vm, info) {
reportToSentry('pipeline_tabs', `error: ${err}, info: ${info}`);
diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js
index f4d9a44a754..6dccdb1a3e6 100644
--- a/app/assets/javascripts/pipelines/pipelines_index.js
+++ b/app/assets/javascripts/pipelines/pipelines_index.js
@@ -40,6 +40,8 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params,
ciRunnerSettingsPath,
anyRunnersAvailable,
+ iosRunnersAvailable,
+ registrationToken,
} = el.dataset;
return new Vue({
@@ -49,6 +51,9 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
artifactsEndpoint,
artifactsEndpointPlaceholder,
suggestedCiTemplates: JSON.parse(suggestedCiTemplates),
+ ciRunnerSettingsPath,
+ anyRunnersAvailable: parseBoolean(anyRunnersAvailable),
+ iosRunnersAvailable: parseBoolean(iosRunnersAvailable),
},
data() {
return {
@@ -78,8 +83,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
projectId,
defaultBranchName,
params: JSON.parse(params),
- ciRunnerSettingsPath,
- anyRunnersAvailable: parseBoolean(anyRunnersAvailable),
+ registrationToken,
},
});
},
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
index b7f590a7b3c..f0556f3d12e 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -38,11 +38,7 @@ export const fetchTestSuite = ({ state, commit, dispatch }, index) => {
return axios
.get(state.suiteEndpoint, { params: { build_ids } })
.then(({ data }) => commit(types.SET_SUITE, { suite: data, index }))
- .catch(() => {
- createFlash({
- message: s__('TestReports|There was an error fetching the test suite.'),
- });
- })
+ .catch((error) => commit(types.SET_SUITE_ERROR, error))
.finally(() => {
dispatch('toggleLoading');
});
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/constants.js b/app/assets/javascripts/pipelines/stores/test_reports/constants.js
new file mode 100644
index 00000000000..8eebfb6b208
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/constants.js
@@ -0,0 +1 @@
+export const ARTIFACTS_EXPIRED_ERROR_MESSAGE = 'Test report artifacts have expired';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
index 03680de0fa9..e6a88bb4175 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
@@ -1,4 +1,5 @@
import { addIconStatus, formatFilePath, formattedTime } from './utils';
+import { ARTIFACTS_EXPIRED_ERROR_MESSAGE } from './constants';
export const getTestSuites = (state) => {
const { test_suites: testSuites = [] } = state.testReports;
@@ -29,3 +30,6 @@ export const getSuiteTests = (state) => {
};
export const getSuiteTestCount = (state) => getSelectedSuite(state)?.test_cases?.length || 0;
+
+export const getSuiteArtifactsExpired = (state) =>
+ state.errorMessage === ARTIFACTS_EXPIRED_ERROR_MESSAGE;
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
index 803f6bf60b1..7651a2f4327 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
@@ -2,4 +2,5 @@ export const SET_PAGE = 'SET_PAGE';
export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
export const SET_SUMMARY = 'SET_SUMMARY';
export const SET_SUITE = 'SET_SUITE';
+export const SET_SUITE_ERROR = 'SET_SUITE_ERROR';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
index cf0bf8483dd..68ee063dda7 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
@@ -1,3 +1,5 @@
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
import * as types from './mutation_types';
export default {
@@ -13,6 +15,18 @@ export default {
state.testReports.test_suites[index] = { ...suite, hasFullSuite: true };
},
+ [types.SET_SUITE_ERROR](state, error) {
+ const errorMessage = error.response?.data?.errors;
+
+ if (errorMessage) {
+ state.errorMessage = errorMessage;
+ } else {
+ createFlash({
+ message: s__('TestReports|There was an error fetching the test suite.'),
+ });
+ }
+ },
+
[types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
Object.assign(state, { selectedSuiteIndex });
},
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js
index 0ee6f53fa58..3ec9418c14e 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/state.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js
@@ -5,6 +5,7 @@ export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) =>
testReports: {},
selectedSuiteIndex: null,
isLoading: false,
+ errorMessage: null,
pageInfo: {
page: 1,
perPage: 20,
diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js
index f6e1c8b7412..588d15495ab 100644
--- a/app/assets/javascripts/pipelines/utils.js
+++ b/app/assets/javascripts/pipelines/utils.js
@@ -1,7 +1,12 @@
import * as Sentry from '@sentry/browser';
import { pickBy } from 'lodash';
-import { SUPPORTED_FILTER_PARAMETERS, NEEDS_PROPERTY } from './constants';
-
+import { getParameterValues } from '~/lib/utils/url_utility';
+import {
+ NEEDS_PROPERTY,
+ SUPPORTED_FILTER_PARAMETERS,
+ TAB_QUERY_PARAM,
+ validPipelineTabNames,
+} from './constants';
/*
The following functions are the main engine in transforming the data as
received from the endpoint into the format the d3 graph expects.
@@ -138,3 +143,13 @@ export const reportMessageToSentry = (component, message, context) => {
Sentry.captureMessage(message);
});
};
+
+export const getPipelineDefaultTab = (url) => {
+ const [tabQueryValue] = getParameterValues(TAB_QUERY_PARAM, url);
+
+ if (tabQueryValue && validPipelineTabNames.includes(tabQueryValue)) {
+ return tabQueryValue;
+ }
+
+ return null;
+};
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
index 09dbf2cee04..ad80032c551 100644
--- a/app/assets/javascripts/project_select_combo_button.js
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { sprintf, __ } from '~/locale';
+import { sanitizeUrl } from '~/lib/utils/url_utility';
import AccessorUtilities from './lib/utils/accessor';
import { loadCSSFile } from './lib/utils/css_utils';
@@ -80,7 +81,7 @@ export default class ProjectSelectComboButton {
setNewItemBtnAttributes(project) {
if (project) {
- this.newItemBtn.attr('href', project.url);
+ this.newItemBtn.attr('href', sanitizeUrl(project.url));
this.newItemBtn.text(
sprintf(__('New %{type} in %{project}'), {
type: this.resourceLabel,
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
index 8511f9bdb0f..9bd78b7c89e 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
@@ -121,20 +121,18 @@ export default {
</script>
<template>
- <div>
+ <div class="gl-pt-2">
<gl-loading-icon v-if="$apollo.queries.pipeline.loading" />
- <div v-else>
+ <div v-else class="gl-align-items-center gl-display-flex">
<linked-pipelines-mini-list
v-if="upstreamPipeline"
- :triggered-by="[upstreamPipeline]"
+ :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+ upstreamPipeline,
+ ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
data-testid="commit-box-mini-graph-upstream"
/>
- <pipeline-mini-graph
- :stages="formattedStages"
- class="gl-display-inline"
- data-testid="commit-box-mini-graph"
- />
+ <pipeline-mini-graph :stages="formattedStages" data-testid="commit-box-mini-graph" />
<linked-pipelines-mini-list
v-if="hasDownstream"
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 3e1c471f015..2bf13941f6f 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import { debounce } from 'lodash';
-import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
+import DEFAULT_PROJECT_TEMPLATES from 'any_else_ce/projects/default_project_templates';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
import { ENTER_KEY } from '../lib/utils/keys';
@@ -286,9 +286,6 @@ const bindEvents = () => {
});
$('.js-import-git-toggle-button').on('click', () => {
- const $projectMirror = $('#project_mirror');
-
- $projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
setProjectNamePathHandlers(
$('.tab-pane.active #project_name'),
$('.tab-pane.active #project_path'),
diff --git a/app/assets/javascripts/projects/settings/init_access_dropdown.js b/app/assets/javascripts/projects/settings/init_access_dropdown.js
index 11272652b63..941efaef3bc 100644
--- a/app/assets/javascripts/projects/settings/init_access_dropdown.js
+++ b/app/assets/javascripts/projects/settings/init_access_dropdown.js
@@ -4,7 +4,7 @@ import AccessDropdown from './components/access_dropdown.vue';
export const initAccessDropdown = (el, options) => {
if (!el) {
- return false;
+ return null;
}
const { accessLevelsData, accessLevel } = options;
diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
index d4c97cbf038..9c8de9bef2d 100644
--- a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
+++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue
@@ -85,7 +85,7 @@ export default {
<gl-avatar-labeled
:src="dropdownItem.avatarUrl"
:entity-name="dropdownItem.name"
- :label="dropdownItem.name"
+ :label="dropdownItem.title"
:size="32"
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
/>
diff --git a/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql
index b193165062a..0c0a874d950 100644
--- a/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql
+++ b/app/assets/javascripts/projects/settings/topics/queries/project_topics_search.query.graphql
@@ -3,6 +3,7 @@ query searchProjectTopics($search: String) {
nodes {
id
name
+ title
avatarUrl
}
}
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
index e5ddfe82e3b..8a9a0b541f3 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
@@ -128,7 +128,7 @@ export default {
this.selectedTemplate = selectedTemplate;
},
validateProjectKey() {
- if (this.projectKey && !new RegExp(/^[a-z0-9_]+$/).test(this.projectKey)) {
+ if (this.projectKey && !/^[a-z0-9_]+$/.test(this.projectKey)) {
this.projectKeyError = __('Only use lowercase letters, numbers, and underscores.');
return;
}
@@ -270,18 +270,16 @@ export default {
</template>
</gl-form-group>
- <div class="gl-display-flex gl-justify-content-end">
- <gl-button
- variant="success"
- class="gl-mt-5"
- data-testid="save_service_desk_settings_button"
- data-qa-selector="save_service_desk_settings_button"
- :disabled="isTemplateSaving"
- @click="onSaveTemplate"
- >
- {{ __('Save changes') }}
- </gl-button>
- </div>
+ <gl-button
+ variant="confirm"
+ class="gl-mt-5"
+ data-testid="save_service_desk_settings_button"
+ data-qa-selector="save_service_desk_settings_button"
+ :disabled="isTemplateSaving"
+ @click="onSaveTemplate"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/prometheus_alerts/components/reset_key.vue b/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
deleted file mode 100644
index befbca48736..00000000000
--- a/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<script>
-import {
- GlButton,
- GlFormGroup,
- GlFormInput,
- GlModal,
- GlModalDirective,
- GlSprintf,
- GlLink,
-} from '@gitlab/ui';
-import createFlash from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { __ } from '~/locale';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-
-export default {
- copyToClipboard: __('Copy'),
- components: {
- GlButton,
- GlFormGroup,
- GlFormInput,
- GlModal,
- ClipboardButton,
- GlSprintf,
- GlLink,
- },
- directives: {
- 'gl-modal': GlModalDirective,
- },
- props: {
- initialAuthorizationKey: {
- type: String,
- required: false,
- default: '',
- },
- changeKeyUrl: {
- type: String,
- required: true,
- },
- notifyUrl: {
- type: String,
- required: true,
- },
- learnMoreUrl: {
- type: String,
- required: true,
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- return {
- authorizationKey: this.initialAuthorizationKey,
- };
- },
- methods: {
- resetKey() {
- axios
- .post(this.changeKeyUrl)
- .then((res) => {
- this.authorizationKey = res.data.token;
- })
- .catch(() => {
- createFlash({
- message: __('Failed to reset key. Please try again.'),
- });
- });
- },
- },
-};
-</script>
-
-<template>
- <div class="row py-4 border-top js-prometheus-alerts">
- <div class="col-lg-3">
- <h4 class="mt-0">
- {{ __('Alerts') }}
- </h4>
- <p>
- {{ __('Receive alerts from manually configured Prometheus servers.') }}
- </p>
- </div>
- <div class="col-lg-9">
- <gl-sprintf
- :message="
- __(
- 'To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab.',
- )
- "
- >
- <template #link="{ content }">
- <gl-link :href="learnMoreUrl" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- <gl-form-group :label="__('URL')" label-for="notify-url" label-class="label-bold">
- <div class="input-group">
- <gl-form-input id="notify-url" :readonly="true" :value="notifyUrl" />
- <span class="input-group-append">
- <clipboard-button
- :text="notifyUrl"
- :title="$options.copyToClipboard"
- :disabled="disabled"
- />
- </span>
- </div>
- </gl-form-group>
- <gl-form-group
- :label="__('Authorization key')"
- label-for="authorization-key"
- label-class="label-bold"
- >
- <div class="input-group">
- <gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" />
- <span class="input-group-append">
- <clipboard-button
- :text="authorizationKey"
- :title="$options.copyToClipboard"
- :disabled="disabled"
- />
- </span>
- </div>
- </gl-form-group>
- <template v-if="authorizationKey.length > 0">
- <gl-modal
- modal-id="authKeyModal"
- :title="__('Reset authorization key?')"
- :ok-title="__('Reset authorization key')"
- ok-variant="danger"
- @ok="resetKey"
- >
- {{
- __(
- 'Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key.',
- )
- }}
- </gl-modal>
- <gl-button v-gl-modal.authKeyModal class="js-reset-auth-key" :disabled="disabled">{{
- __('Reset key')
- }}</gl-button>
- </template>
- <gl-button v-else :disabled="disabled" class="js-reset-auth-key" @click="resetKey">{{
- __('Generate key')
- }}</gl-button>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/prometheus_alerts/index.js b/app/assets/javascripts/prometheus_alerts/index.js
deleted file mode 100644
index 7efe6ed186b..00000000000
--- a/app/assets/javascripts/prometheus_alerts/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue';
-import ResetKey from './components/reset_key.vue';
-
-export default () => {
- const el = document.querySelector('#js-settings-prometheus-alerts');
-
- if (!el) {
- return;
- }
-
- const { authorizationKey, changeKeyUrl, notifyUrl, learnMoreUrl, disabled } = el.dataset;
-
- // eslint-disable-next-line no-new
- new Vue({
- el,
- render(createElement) {
- return createElement(ResetKey, {
- props: {
- initialAuthorizationKey: authorizationKey,
- changeKeyUrl,
- notifyUrl,
- learnMoreUrl,
- disabled,
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/related_issues/components/add_issuable_form.vue b/app/assets/javascripts/related_issues/components/add_issuable_form.vue
index 9ee2e7a4ffd..42de419aec4 100644
--- a/app/assets/javascripts/related_issues/components/add_issuable_form.vue
+++ b/app/assets/javascripts/related_issues/components/add_issuable_form.vue
@@ -195,7 +195,10 @@ export default {
:path-id-separator="pathIdSeparator"
:input-value="inputValue"
:auto-complete-sources="transformedAutocompleteSources"
- :auto-complete-options="{ issues: autoCompleteIssues, epics: autoCompleteEpics }"
+ :auto-complete-options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ issues: autoCompleteIssues,
+ epics: autoCompleteEpics,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:issuable-type="issuableType"
@pendingIssuableRemoveRequest="onPendingIssuableRemoveRequest"
@formCancel="onFormCancel"
diff --git a/app/assets/javascripts/related_issues/components/related_issues_root.vue b/app/assets/javascripts/related_issues/components/related_issues_root.vue
index 40d58c04753..da049d68467 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_root.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_root.vue
@@ -220,7 +220,8 @@ export default {
const startsWithNumber = String(touchedReference).match(/^[0-9]/) !== null;
if (startsWithNumber) {
- this.inputValue = `#${touchedReference}`;
+ const { pathIdSeparator } = this;
+ this.inputValue = `${pathIdSeparator}${touchedReference}`;
} else {
this.inputValue = `${touchedReference}`;
}
diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue
index 8365e6a5ab0..327da1fb2a1 100644
--- a/app/assets/javascripts/releases/components/app_edit_new.vue
+++ b/app/assets/javascripts/releases/components/app_edit_new.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlFormInput, GlFormGroup, GlSprintf } from '@gitlab/ui';
+import { GlButton, GlFormCheckbox, GlFormInput, GlFormGroup, GlLink, GlSprintf } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { isSameOriginUrl, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
@@ -12,9 +12,11 @@ import TagField from './tag_field.vue';
export default {
name: 'ReleaseEditNewApp',
components: {
+ GlFormCheckbox,
GlFormInput,
GlFormGroup,
GlButton,
+ GlLink,
GlSprintf,
MarkdownField,
AssetLinksForm,
@@ -28,6 +30,7 @@ export default {
'fetchError',
'markdownDocsPath',
'markdownPreviewPath',
+ 'editReleaseDocsPath',
'releasesPagePath',
'release',
'newMilestonePath',
@@ -35,8 +38,9 @@ export default {
'projectId',
'groupId',
'groupMilestonesAvailable',
+ 'tagNotes',
]),
- ...mapGetters('editNew', ['isValid', 'isExistingRelease']),
+ ...mapGetters('editNew', ['isValid', 'isExistingRelease', 'formattedReleaseNotes']),
showForm() {
return Boolean(!this.isFetchingRelease && !this.fetchError && this.release);
},
@@ -64,6 +68,14 @@ export default {
this.updateReleaseMilestones(milestones);
},
},
+ includeTagNotes: {
+ get() {
+ return this.$store.state.editNew.includeTagNotes;
+ },
+ set(includeTagNotes) {
+ this.updateIncludeTagNotes(includeTagNotes);
+ },
+ },
cancelPath() {
const backUrl = getParameterByName(BACK_URL_PARAM);
@@ -105,6 +117,7 @@ export default {
'updateReleaseTitle',
'updateReleaseNotes',
'updateReleaseMilestones',
+ 'updateIncludeTagNotes',
]),
submitForm() {
if (!this.isFormSubmissionDisabled) {
@@ -161,7 +174,7 @@ export default {
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:add-spacing-classes="false"
- :textarea-value="releaseNotes"
+ :textarea-value="formattedReleaseNotes"
class="gl-mt-3 gl-mb-3"
>
<template #textarea>
@@ -178,6 +191,25 @@ export default {
</markdown-field>
</div>
</gl-form-group>
+ <gl-form-group v-if="!isExistingRelease">
+ <gl-form-checkbox v-model="includeTagNotes">
+ {{ s__('Release|Include message from the annotated tag.') }}
+
+ <template #help>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Release|You can edit the content later by editing the release. %{linkStart}How do I edit a release?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="editReleaseDocsPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
+ </gl-form-checkbox>
+ </gl-form-group>
<asset-links-form />
diff --git a/app/assets/javascripts/releases/components/tag_field_new.vue b/app/assets/javascripts/releases/components/tag_field_new.vue
index 9e05d00a98d..d3b6d07590f 100644
--- a/app/assets/javascripts/releases/components/tag_field_new.vue
+++ b/app/assets/javascripts/releases/components/tag_field_new.vue
@@ -52,7 +52,10 @@ export default {
},
},
showTagNameValidationError() {
- return this.isInputDirty && this.validationErrors.isTagNameEmpty;
+ return (
+ this.isInputDirty &&
+ (this.validationErrors.isTagNameEmpty || this.validationErrors.existingRelease)
+ );
},
tagNameInputId() {
return uniqueId('tag-name-input-');
@@ -60,9 +63,14 @@ export default {
createFromSelectorId() {
return uniqueId('create-from-selector-');
},
+ tagFeedback() {
+ return this.validationErrors.existingRelease
+ ? __('Selected tag is already in use. Choose another option.')
+ : __('Tag name is required.');
+ },
},
methods: {
- ...mapActions('editNew', ['updateReleaseTagName', 'updateCreateFrom']),
+ ...mapActions('editNew', ['updateReleaseTagName', 'updateCreateFrom', 'fetchTagNotes']),
markInputAsDirty() {
this.isInputDirty = true;
},
@@ -112,7 +120,7 @@ export default {
<gl-form-group
data-testid="tag-name-field"
:state="!showTagNameValidationError"
- :invalid-feedback="__('Tag name is required')"
+ :invalid-feedback="tagFeedback"
:label="$options.translations.tagName.label"
:label-for="tagNameInputId"
:label-description="$options.translations.tagName.labelDescription"
@@ -125,6 +133,7 @@ export default {
:translations="$options.translations.tagName"
:enabled-ref-types="$options.tagNameEnabledRefTypes"
:state="!showTagNameValidationError"
+ @input="fetchTagNotes"
@hide.once="markInputAsDirty"
>
<template #footer="{ isLoading, matches, query }">
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
index b3ba4f9263a..08197377f61 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js
@@ -1,3 +1,4 @@
+import { getTag } from '~/rest_api';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
@@ -7,6 +8,7 @@ import deleteReleaseAssetLinkMutation from '~/releases/graphql/mutations/delete_
import updateReleaseMutation from '~/releases/graphql/mutations/update_release.mutation.graphql';
import oneReleaseForEditingQuery from '~/releases/graphql/queries/one_release_for_editing.query.graphql';
import { gqClient, convertOneReleaseGraphQLResponse } from '~/releases/util';
+
import * as types from './mutation_types';
export const initializeRelease = ({ commit, dispatch, getters }) => {
@@ -224,3 +226,23 @@ export const updateRelease = async ({ commit, dispatch, state, getters }) => {
});
}
};
+
+export const fetchTagNotes = ({ commit, state }, tagName) => {
+ commit(types.REQUEST_TAG_NOTES);
+
+ return getTag(state.projectId, tagName)
+ .then(({ data }) => {
+ commit(types.RECEIVE_TAG_NOTES_SUCCESS, data);
+ })
+ .catch((error) => {
+ createFlash({
+ message: s__('Release|Unable to fetch the tag notes.'),
+ });
+
+ commit(types.RECEIVE_TAG_NOTES_ERROR, error);
+ });
+};
+
+export const updateIncludeTagNotes = ({ commit }, includeTagNotes) => {
+ commit(types.UPDATE_INCLUDE_TAG_NOTES, includeTagNotes);
+};
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
index d4f49e53619..0ca5eb9931a 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/getters.js
@@ -1,4 +1,5 @@
import { isEmpty } from 'lodash';
+import { s__ } from '~/locale';
import { hasContent } from '~/lib/utils/text_utility';
import { getDuplicateItemsFromArray } from '~/lib/utils/array_utility';
@@ -52,6 +53,10 @@ export const validationErrors = (state) => {
errors.isTagNameEmpty = true;
}
+ if (state.existingRelease) {
+ errors.existingRelease = true;
+ }
+
// Each key of this object is a URL, and the value is an
// array of Release link objects that share this URL.
// This is used for detecting duplicate URLs.
@@ -113,11 +118,15 @@ export const validationErrors = (state) => {
/** Returns whether or not the release object is valid */
export const isValid = (_state, getters) => {
const errors = getters.validationErrors;
- return Object.values(errors.assets.links).every(isEmpty) && !errors.isTagNameEmpty;
+ return (
+ Object.values(errors.assets.links).every(isEmpty) &&
+ !errors.isTagNameEmpty &&
+ !errors.existingRelease
+ );
};
/** Returns all the variables for a `releaseUpdate` GraphQL mutation */
-export const releaseUpdateMutatationVariables = (state) => {
+export const releaseUpdateMutatationVariables = (state, getters) => {
const name = state.release.name?.trim().length > 0 ? state.release.name.trim() : null;
// Milestones may be either a list of milestone objects OR just a list
@@ -129,7 +138,9 @@ export const releaseUpdateMutatationVariables = (state) => {
projectPath: state.projectPath,
tagName: state.release.tagName,
name,
- description: state.release.description,
+ description: state.includeTagNotes
+ ? getters.formattedReleaseNotes
+ : state.release.description,
milestones,
},
};
@@ -151,3 +162,8 @@ export const releaseCreateMutatationVariables = (state, getters) => {
},
};
};
+
+export const formattedReleaseNotes = ({ includeTagNotes, release: { description }, tagNotes }) =>
+ includeTagNotes && tagNotes
+ ? `${description}\n\n### ${s__('Releases|Tag message')}\n\n${tagNotes}\n`
+ : description;
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js b/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
index 1b2f5f33f02..daa077309a1 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/mutation_types.js
@@ -20,3 +20,9 @@ export const UPDATE_ASSET_LINK_URL = 'UPDATE_ASSET_LINK_URL';
export const UPDATE_ASSET_LINK_NAME = 'UPDATE_ASSET_LINK_NAME';
export const UPDATE_ASSET_LINK_TYPE = 'UPDATE_ASSET_LINK_TYPE';
export const REMOVE_ASSET_LINK = 'REMOVE_ASSET_LINK';
+
+export const REQUEST_TAG_NOTES = 'REQUEST_TAG_NOTES';
+export const RECEIVE_TAG_NOTES_SUCCESS = 'RECEIVE_TAG_NOTES_SUCCESS';
+export const RECEIVE_TAG_NOTES_ERROR = 'RECEIVE_TAG_NOTES_ERROR';
+
+export const UPDATE_INCLUDE_TAG_NOTES = 'UPDATE_INCLUDE_TAG_NOTES';
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
index cf282f9ab2c..6b22468bbfe 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/mutations.js
@@ -95,4 +95,22 @@ export default {
[types.REMOVE_ASSET_LINK](state, linkIdToRemove) {
state.release.assets.links = state.release.assets.links.filter((l) => l.id !== linkIdToRemove);
},
+
+ [types.REQUEST_TAG_NOTES](state) {
+ state.isFetchingTagNotes = true;
+ },
+ [types.RECEIVE_TAG_NOTES_SUCCESS](state, data) {
+ state.fetchError = undefined;
+ state.isFetchingTagNotes = false;
+ state.tagNotes = data.message;
+ state.existingRelease = data.release;
+ },
+ [types.RECEIVE_TAG_NOTES_ERROR](state, error) {
+ state.fetchError = error;
+ state.isFetchingTagNotes = false;
+ state.tagNotes = '';
+ },
+ [types.UPDATE_INCLUDE_TAG_NOTES](state, includeTagNotes) {
+ state.includeTagNotes = includeTagNotes;
+ },
};
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/state.js b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
index 315d07ac664..33cb3ee06d0 100644
--- a/app/assets/javascripts/releases/stores/modules/edit_new/state.js
+++ b/app/assets/javascripts/releases/stores/modules/edit_new/state.js
@@ -9,6 +9,7 @@ export default ({
manageMilestonesPath,
newMilestonePath,
releasesPagePath,
+ editReleaseDocsPath,
tagName = null,
defaultBranch = null,
@@ -23,6 +24,7 @@ export default ({
manageMilestonesPath,
newMilestonePath,
releasesPagePath,
+ editReleaseDocsPath,
/**
* The name of the tag associated with the release, provided by the backend.
@@ -48,4 +50,8 @@ export default ({
isUpdatingRelease: false,
updateError: null,
+
+ tagNotes: '',
+ includeTagNotes: false,
+ existingRelease: null,
});
diff --git a/app/assets/javascripts/reports/codequality_report/store/getters.js b/app/assets/javascripts/reports/codequality_report/store/getters.js
index 4712f8cbefe..70d11e96a54 100644
--- a/app/assets/javascripts/reports/codequality_report/store/getters.js
+++ b/app/assets/javascripts/reports/codequality_report/store/getters.js
@@ -1,5 +1,5 @@
import { spriteIcon } from '~/lib/utils/common_utils';
-import { sprintf, s__ } from '~/locale';
+import { sprintf, s__, n__ } from '~/locale';
import { LOADING, ERROR, SUCCESS, STATUS_NOT_FOUND } from '../../constants';
export const hasCodequalityIssues = (state) =>
@@ -29,9 +29,17 @@ export const codequalityText = (state) => {
},
);
} else if (resolvedIssues.length) {
- text = s__(`ciReport|Code quality improved`);
+ text = n__(
+ `ciReport|Code quality improved due to 1 resolved issue`,
+ `ciReport|Code quality improved due to %d resolved issues`,
+ resolvedIssues.length,
+ );
} else if (newIssues.length) {
- text = s__(`ciReport|Code quality degraded`);
+ text = n__(
+ `ciReport|Code quality degraded due to 1 new issue`,
+ `ciReport|Code quality degraded due to %d new issues`,
+ newIssues.length,
+ );
}
return text;
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index c9e4aab1db1..3729bd4c601 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -8,7 +8,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo, getLocationHash } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import CodeIntelligence from '~/code_navigation/components/app.vue';
@@ -17,15 +17,12 @@ import getRefMixin from '../mixins/get_ref';
import blobInfoQuery from '../queries/blob_info.query.graphql';
import userInfoQuery from '../queries/user_info.query.graphql';
import applicationInfoQuery from '../queries/application_info.query.graphql';
-import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants';
+import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE, LEGACY_FILE_TYPES } from '../constants';
import BlobButtonGroup from './blob_button_group.vue';
import ForkSuggestion from './fork_suggestion.vue';
import { loadViewer } from './blob_viewers';
export default {
- i18n: {
- pipelineEditor: __('Pipeline Editor'),
- },
components: {
BlobHeader,
BlobButtonGroup,
@@ -132,7 +129,8 @@ export default {
return this.shouldLoadLegacyViewer ? null : loadViewer(fileType, this.isUsingLfs);
},
shouldLoadLegacyViewer() {
- return this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs;
+ const isTextFile = this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs;
+ return isTextFile || LEGACY_FILE_TYPES.includes(this.blobInfo.fileType);
},
legacyViewerLoaded() {
return (
@@ -199,11 +197,20 @@ export default {
this.legacyRichViewer = html;
}
+ this.scrollToHash();
this.isBinary = binary;
this.isLoadingLegacyViewer = false;
})
.catch(() => this.displayError());
},
+ scrollToHash() {
+ const hash = getLocationHash();
+ if (hash) {
+ // Ensures the browser's native scroll to hash is triggered for async content
+ window.location.hash = '';
+ window.location.hash = hash;
+ }
+ },
displayError() {
createFlash({ message: __('An error occurred while loading the file. Please try again.') });
},
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 84c9f9d0bbe..20888db80a9 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -269,6 +269,9 @@ export default {
renderAddToTreeDropdown() {
return !this.isBlobPath && (this.canCollaborate || this.canCreateMrFromFork);
},
+ newDirectoryPath() {
+ return joinPaths(this.newDirPath, this.currentPath);
+ },
},
methods: {
isLast(i) {
@@ -332,7 +335,7 @@ export default {
:commit-message="__('Add new directory')"
:target-branch="selectedBranch"
:original-branch="originalBranch"
- :path="newDirPath"
+ :path="newDirectoryPath"
/>
</nav>
</template>
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 2810db33e64..03dd7c6fada 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -14,6 +14,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
@@ -27,6 +28,7 @@ export default {
GlButtonGroup,
GlLink,
GlLoadingIcon,
+ UserAvatarImage,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -111,24 +113,24 @@ export default {
</script>
<template>
- <div class="well-segment commit gl-p-5 gl-w-full">
+ <div class="well-segment commit gl-p-5 gl-w-full gl-display-flex">
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
<template v-else-if="commit">
<user-avatar-link
v-if="commit.author"
:link-href="commit.author.webPath"
:img-src="commit.author.avatarUrl"
- :img-size="40"
- class="avatar-cell"
+ :img-size="32"
+ :img-css-classes="'gl-mr-0!' /* NOTE: this is needed only while we migrate user-avatar-image to GlAvatar (7731 epics) */"
+ class="gl-my-2 gl-mr-4"
+ />
+ <user-avatar-image
+ v-else
+ class="gl-my-2 gl-mr-4"
+ :img-src="commit.authorGravatar || $options.defaultAvatarUrl"
+ :css-classes="'gl-mr-0!' /* NOTE: this is needed only while we migrate user-avatar-image to GlAvatar (7731 epics) */"
+ :size="32"
/>
- <span v-else class="avatar-cell user-avatar-link">
- <img
- :src="commit.authorGravatar || $options.defaultAvatarUrl"
- width="40"
- height="40"
- class="avatar s40"
- />
- </span>
<div class="commit-detail flex-list">
<div class="commit-content qa-commit-content">
<gl-link
@@ -168,7 +170,10 @@ export default {
class="commit-row-description gl-mb-3"
></pre>
</div>
- <div class="commit-actions flex-row">
+ <div class="gl-flex-grow-1"></div>
+ <div
+ class="commit-actions gl-display-flex gl-flex-align gl-align-items-center gl-flex-direction-row"
+ >
<div
v-if="commit.signatureHtml"
v-html="commit.signatureHtml /* eslint-disable-line vue/no-v-html */"
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index bb9d3180be8..2cafeed2ef4 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -86,3 +86,24 @@ export const DEFAULT_BLOB_INFO = {
export const TEXT_FILE_TYPE = 'text';
export const LFS_STORAGE = 'lfs';
+
+/**
+ * We have some features (like linking to external dependencies) that our frontend highlighter
+ * do not yet support.
+ * These are file types that we want the legacy (backend) syntax highlighter to highlight.
+ */
+export const LEGACY_FILE_TYPES = [
+ 'package_json',
+ 'gemfile',
+ 'gemspec',
+ 'composer_json',
+ 'podfile',
+ 'podspec',
+ 'podspec_json',
+ 'cartfile',
+ 'godeps_json',
+ 'requirements_txt',
+ 'cargo_toml',
+ 'go_mod',
+ 'go_sum',
+];
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index b38a1cfdc7b..8f8735a6371 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -151,12 +151,20 @@ export default function setupVueRepositoryList() {
const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
const { historyLink } = treeHistoryLinkEl.dataset;
+ let { isProjectOverview } = treeHistoryLinkEl.dataset;
+
+ const isProjectOverviewAfterEach = router.afterEach(() => {
+ isProjectOverview = false;
+ isProjectOverviewAfterEach();
+ });
// eslint-disable-next-line no-new
new Vue({
el: treeHistoryLinkEl,
router,
render(h) {
+ if (parseBoolean(isProjectOverview) && !this.$route.params.path) return null;
+
return h(
GlButton,
{
diff --git a/app/assets/javascripts/rest_api.js b/app/assets/javascripts/rest_api.js
index 48a15954035..0f8e6945bf9 100644
--- a/app/assets/javascripts/rest_api.js
+++ b/app/assets/javascripts/rest_api.js
@@ -4,6 +4,7 @@ export * from './api/user_api';
export * from './api/markdown_api';
export * from './api/bulk_imports_api';
export * from './api/namespaces_api';
+export * from './api/tags_api';
// Note: It's not possible to spy on methods imported from this file in
// Jest tests.
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 009afe03ea6..a3abc8b8e90 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -7,6 +7,18 @@ import createFlash from './flash';
import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale';
+const updateSidebarClasses = (layoutPage, rightSidebar) => {
+ if (window.innerWidth >= 768) {
+ layoutPage.classList.remove('right-sidebar-expanded', 'right-sidebar-collapsed');
+ rightSidebar.classList.remove('right-sidebar-collapsed');
+ rightSidebar.classList.add('right-sidebar-expanded');
+ } else {
+ layoutPage.classList.add('right-sidebar-collapsed', 'is-merge-request');
+ rightSidebar.classList.add('right-sidebar-collapsed');
+ rightSidebar.classList.remove('right-sidebar-expanded');
+ }
+};
+
function Sidebar() {
this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside');
@@ -42,13 +54,22 @@ Sidebar.prototype.addEventListeners = function () {
this.sidebar.on('hiddenGlDropdown', this, this.onSidebarDropdownHidden);
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
- return $(document)
- .off('click', '.js-issuable-todo')
- .on('click', '.js-issuable-todo', this.toggleTodo);
+ $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
+
+ if (window.gon?.features?.movedMrSidebar) {
+ const layoutPage = document.querySelector('.layout-page');
+ const rightSidebar = document.querySelector('.js-right-sidebar');
+
+ updateSidebarClasses(layoutPage, rightSidebar);
+ window.addEventListener('resize', () => updateSidebarClasses(layoutPage, rightSidebar));
+ }
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
const $this = $(this);
+
+ if ($this.hasClass('right-sidebar-merge-requests')) return;
+
const $collapseIcon = $('.js-sidebar-collapse');
const $expandIcon = $('.js-sidebar-expand');
const $toggleContainer = $('.js-sidebar-toggle-container');
diff --git a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
index c2db3b9facd..40787cf72da 100644
--- a/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
@@ -5,7 +5,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '../components/runner_header.vue';
import RunnerUpdateForm from '../components/runner_update_form.vue';
import { I18N_FETCH_ERROR } from '../constants';
-import runnerQuery from '../graphql/details/runner.query.graphql';
+import runnerFormQuery from '../graphql/edit/runner_form.query.graphql';
import { captureException } from '../sentry_utils';
export default {
@@ -19,6 +19,11 @@ export default {
type: String,
required: true,
},
+ runnerPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -27,7 +32,7 @@ export default {
},
apollo: {
runner: {
- query: runnerQuery,
+ query: runnerFormQuery,
variables() {
return {
id: convertToGraphQLId(TYPE_CI_RUNNER, this.runnerId),
@@ -40,6 +45,11 @@ export default {
},
},
},
+ computed: {
+ loading() {
+ return this.$apollo.queries.runner.loading;
+ },
+ },
errorCaptured(error) {
this.reportToSentry(error);
},
@@ -53,6 +63,11 @@ export default {
<template>
<div>
<runner-header v-if="runner" :runner="runner" />
- <runner-update-form :runner="runner" class="gl-my-5" />
+ <runner-update-form
+ :loading="loading"
+ :runner="runner"
+ :runner-path="runnerPath"
+ class="gl-my-5"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/admin_runner_edit/index.js b/app/assets/javascripts/runner/admin_runner_edit/index.js
index adb420f9963..a2ac5731a62 100644
--- a/app/assets/javascripts/runner/admin_runner_edit/index.js
+++ b/app/assets/javascripts/runner/admin_runner_edit/index.js
@@ -12,7 +12,7 @@ export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => {
return null;
}
- const { runnerId } = el.dataset;
+ const { runnerId, runnerPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -25,6 +25,7 @@ export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => {
return h(AdminRunnerEditApp, {
props: {
runnerId,
+ runnerPath,
},
});
},
diff --git a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
index 86ad912f017..c3f317b40b0 100644
--- a/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_show/admin_runner_show_app.vue
@@ -1,19 +1,23 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { redirectTo } from '~/lib/utils/url_utility';
+import RunnerDeleteButton from '../components/runner_delete_button.vue';
import RunnerEditButton from '../components/runner_edit_button.vue';
import RunnerPauseButton from '../components/runner_pause_button.vue';
import RunnerHeader from '../components/runner_header.vue';
import RunnerDetails from '../components/runner_details.vue';
import { I18N_FETCH_ERROR } from '../constants';
-import runnerQuery from '../graphql/details/runner.query.graphql';
+import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'AdminRunnerShowApp',
components: {
+ RunnerDeleteButton,
RunnerEditButton,
RunnerPauseButton,
RunnerHeader,
@@ -27,6 +31,10 @@ export default {
type: String,
required: true,
},
+ runnersPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -52,6 +60,9 @@ export default {
canUpdate() {
return this.runner.userPermissions?.updateRunner;
},
+ canDelete() {
+ return this.runner.userPermissions?.deleteRunner;
+ },
},
errorCaptured(error) {
this.reportToSentry(error);
@@ -60,6 +71,10 @@ export default {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
+ onDeleted({ message }) {
+ saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
+ redirectTo(this.runnersPath);
+ },
},
};
</script>
@@ -69,6 +84,7 @@ export default {
<template #actions>
<runner-edit-button v-if="canUpdate && runner.editAdminUrl" :href="runner.editAdminUrl" />
<runner-pause-button v-if="canUpdate" :runner="runner" />
+ <runner-delete-button v-if="canDelete" :runner="runner" @deleted="onDeleted" />
</template>
</runner-header>
diff --git a/app/assets/javascripts/runner/admin_runner_show/index.js b/app/assets/javascripts/runner/admin_runner_show/index.js
index a781898cf8d..ea455416648 100644
--- a/app/assets/javascripts/runner/admin_runner_show/index.js
+++ b/app/assets/javascripts/runner/admin_runner_show/index.js
@@ -1,18 +1,21 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
import AdminRunnerShowApp from './admin_runner_show_app.vue';
Vue.use(VueApollo);
export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
+ showAlertFromLocalStorage();
+
const el = document.querySelector(selector);
if (!el) {
return null;
}
- const { runnerId } = el.dataset;
+ const { runnerId, runnersPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -25,6 +28,7 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
return h(AdminRunnerShowApp, {
props: {
runnerId,
+ runnersPath,
},
});
},
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index accc9926a57..c2bb635e056 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -38,7 +38,7 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
-const runnersCountSmartQuery = {
+const countSmartQuery = () => ({
query: runnersAdminCountQuery,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
update(data) {
@@ -47,6 +47,39 @@ const runnersCountSmartQuery = {
error(error) {
this.reportToSentry(error);
},
+});
+
+const tabCountSmartQuery = ({ type }) => {
+ return {
+ ...countSmartQuery(),
+ variables() {
+ return {
+ ...this.countVariables,
+ type,
+ };
+ },
+ };
+};
+
+const statusCountSmartQuery = ({ status, name }) => {
+ return {
+ ...countSmartQuery(),
+ skip() {
+ // skip if filtering by status and not using _this_ status as filter
+ if (this.countVariables.status && this.countVariables.status !== status) {
+ // reset count for given status
+ this[name] = null;
+ return true;
+ }
+ return false;
+ },
+ variables() {
+ return {
+ ...this.countVariables,
+ status,
+ };
+ },
+ };
};
export default {
@@ -101,65 +134,30 @@ export default {
this.reportToSentry(error);
},
},
+
+ // Tabs counts
allRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: null,
- };
- },
+ ...tabCountSmartQuery({ type: null }),
},
instanceRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: INSTANCE_TYPE,
- };
- },
+ ...tabCountSmartQuery({ type: INSTANCE_TYPE }),
},
groupRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: GROUP_TYPE,
- };
- },
+ ...tabCountSmartQuery({ type: GROUP_TYPE }),
},
projectRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: PROJECT_TYPE,
- };
- },
+ ...tabCountSmartQuery({ type: PROJECT_TYPE }),
},
+
+ // Runner stats
onlineRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- status: STATUS_ONLINE,
- };
- },
+ ...statusCountSmartQuery({ status: STATUS_ONLINE, name: 'onlineRunnersTotal' }),
},
offlineRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- status: STATUS_OFFLINE,
- };
- },
+ ...statusCountSmartQuery({ status: STATUS_OFFLINE, name: 'offlineRunnersTotal' }),
},
staleRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- status: STATUS_STALE,
- };
- },
+ ...statusCountSmartQuery({ status: STATUS_STALE, name: 'staleRunnersTotal' }),
},
},
computed: {
@@ -263,12 +261,6 @@ export default {
</script>
<template>
<div>
- <runner-stats
- :online-runners-count="onlineRunnersTotal"
- :offline-runners-count="offlineRunnersTotal"
- :stale-runners-count="staleRunnersTotal"
- />
-
<div
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
>
@@ -300,6 +292,12 @@ export default {
:namespace="$options.filteredSearchNamespace"
/>
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
+
<div v-if="noRunnersFound" class="gl-text-center gl-p-5">
{{ __('No runners found') }}
</div>
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 12e2cb2ee9f..b1d8442bb32 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -5,12 +5,15 @@ import { visitUrl } from '~/lib/utils/url_utility';
import { updateOutdatedUrl } from '~/runner/runner_search_utils';
import createDefaultClient from '~/lib/graphql';
import { createLocalState } from '../graphql/list/local_state';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
import AdminRunnersApp from './admin_runners_app.vue';
Vue.use(GlToast);
Vue.use(VueApollo);
export const initAdminRunners = (selector = '#js-admin-runners') => {
+ showAlertFromLocalStorage();
+
const el = document.querySelector(selector);
if (!el) {
diff --git a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
index 3fbe3c1be74..bb2a8ddf151 100644
--- a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
@@ -96,7 +96,7 @@ export default {
<runner-instructions-modal
v-if="instructionsModalOpened"
ref="runnerInstructionsModal"
- :registration-token="registrationToken"
+ :registration-token="currentRegistrationToken"
data-testid="runner-instructions-modal"
/>
</gl-dropdown-item>
diff --git a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
index 1234054c660..09d46ce3e66 100644
--- a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -115,14 +115,14 @@ export default {
<gl-modal
size="sm"
:modal-id="$options.modalId"
- :action-primary="{
+ :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: $options.i18n.modalAction,
attributes: [{ variant: 'danger' }],
- }"
- :action-secondary="{
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :action-secondary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: $options.i18n.modalCancel,
attributes: [{ variant: 'default' }],
- }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:title="$options.i18n.modalTitle"
@primary="handleModalPrimary"
>
diff --git a/app/assets/javascripts/runner/components/runner_delete_button.vue b/app/assets/javascripts/runner/components/runner_delete_button.vue
index b58665ecbc9..62382891df0 100644
--- a/app/assets/javascripts/runner/components/runner_delete_button.vue
+++ b/app/assets/javascripts/runner/components/runner_delete_button.vue
@@ -165,6 +165,8 @@ export default {
:loading="deleting"
:disabled="disabled"
variant="danger"
+ category="secondary"
+ v-bind="$attrs"
>
{{ buttonContent }}
</gl-button>
diff --git a/app/assets/javascripts/runner/components/runner_details.vue b/app/assets/javascripts/runner/components/runner_details.vue
index b6a5ffc7a64..3734f436034 100644
--- a/app/assets/javascripts/runner/components/runner_details.vue
+++ b/app/assets/javascripts/runner/components/runner_details.vue
@@ -84,6 +84,9 @@ export default {
</runner-detail>
<runner-detail :label="s__('Runners|Version')" :value="runner.version" />
<runner-detail :label="s__('Runners|IP Address')" :value="runner.ipAddress" />
+ <runner-detail :label="s__('Runners|Executor')" :value="runner.executorName" />
+ <runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
+ <runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
<runner-detail :label="s__('Runners|Configuration')">
<template #value>
<gl-intersperse v-if="configTextProtected || configTextUntagged">
diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue
index b25d92d049e..4eb1312b204 100644
--- a/app/assets/javascripts/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/runner/components/runner_jobs.vue
@@ -1,7 +1,7 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { createAlert } from '~/flash';
-import runnerJobsQuery from '../graphql/details/runner_jobs.query.graphql';
+import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql';
import { I18N_FETCH_ERROR, I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '../constants';
import { captureException } from '../sentry_utils';
import { getPaginationVariables } from '../utils';
diff --git a/app/assets/javascripts/runner/components/runner_pagination.vue b/app/assets/javascripts/runner/components/runner_pagination.vue
index b683a7f2330..cfc21d1407b 100644
--- a/app/assets/javascripts/runner/components/runner_pagination.vue
+++ b/app/assets/javascripts/runner/components/runner_pagination.vue
@@ -21,10 +21,10 @@ export default {
},
computed: {
prevPage() {
- return this.pageInfo?.hasPreviousPage ? this.value?.page - 1 : null;
+ return this.pageInfo?.hasPreviousPage ? this.value.page - 1 : null;
},
nextPage() {
- return this.pageInfo?.hasNextPage ? this.value?.page + 1 : null;
+ return this.pageInfo?.hasNextPage ? this.value.page + 1 : null;
},
},
methods: {
diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue
index d080d34fdd3..daca718e2b5 100644
--- a/app/assets/javascripts/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/runner/components/runner_projects.vue
@@ -2,7 +2,7 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { sprintf, formatNumber } from '~/locale';
import { createAlert } from '~/flash';
-import runnerProjectsQuery from '../graphql/details/runner_projects.query.graphql';
+import runnerProjectsQuery from '../graphql/show/runner_projects.query.graphql';
import {
I18N_ASSIGNED_PROJECTS,
I18N_NONE,
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index 119e5236f85..56c9007a781 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -1,10 +1,12 @@
<script>
import {
GlButton,
+ GlIcon,
GlForm,
GlFormCheckbox,
GlFormGroup,
GlFormInputGroup,
+ GlSkeletonLoader,
GlTooltipDirective,
} from '@gitlab/ui';
import {
@@ -12,19 +14,23 @@ import {
runnerToModel,
} from 'ee_else_ce/runner/runner_update_form_utils';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
-import runnerUpdateMutation from '../graphql/details/runner_update.mutation.graphql';
+import runnerUpdateMutation from '../graphql/edit/runner_update.mutation.graphql';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'RunnerUpdateForm',
components: {
GlButton,
+ GlIcon,
GlForm,
GlFormCheckbox,
GlFormGroup,
GlFormInputGroup,
+ GlSkeletonLoader,
RunnerUpdateCostFactorFields: () =>
import('ee_component/runner/components/runner_update_cost_factor_fields.vue'),
},
@@ -37,6 +43,16 @@ export default {
required: false,
default: null,
},
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ runnerPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -48,9 +64,6 @@ export default {
canBeLockedToProject() {
return this.runner?.runnerType === PROJECT_TYPE;
},
- readonlyIpAddress() {
- return this.runner?.ipAddress;
- },
},
watch: {
runner(newVal, oldVal) {
@@ -74,24 +87,23 @@ export default {
});
if (errors?.length) {
- // Validation errors need not be thrown
- createAlert({ message: errors[0] });
- return;
+ this.onError(errors[0]);
+ } else {
+ this.onSuccess();
}
-
- this.onSuccess();
} catch (error) {
const { message } = error;
-
- createAlert({ message });
+ this.onError(message);
captureException({ error, component: this.$options.name });
- } finally {
- this.saving = false;
}
},
onSuccess() {
- createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
- this.model = runnerToModel(this.runner);
+ saveAlertToLocalStorage({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
+ redirectTo(this.runnerPath);
+ },
+ onError(message) {
+ this.saving = false;
+ createAlert({ message });
},
},
ACCESS_LEVEL_NOT_PROTECTED,
@@ -100,104 +112,108 @@ export default {
</script>
<template>
<gl-form @submit.prevent="onSubmit">
- <gl-form-checkbox
- v-model="model.active"
- data-testid="runner-field-paused"
- :value="false"
- :unchecked-value="true"
- >
- {{ __('Paused') }}
- <template #help>
- {{ s__('Runners|Stop the runner from accepting new jobs.') }}
- </template>
- </gl-form-checkbox>
+ <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Details') }}</h4>
- <gl-form-checkbox
- v-model="model.accessLevel"
- data-testid="runner-field-protected"
- :value="$options.ACCESS_LEVEL_REF_PROTECTED"
- :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED"
- >
- {{ __('Protected') }}
- <template #help>
- {{ s__('Runners|Use the runner on pipelines for protected branches only.') }}
- </template>
- </gl-form-checkbox>
+ <gl-skeleton-loader v-if="loading" />
+ <gl-form-group v-else :label="__('Description')" data-testid="runner-field-description">
+ <gl-form-input-group v-model="model.description" />
+ </gl-form-group>
- <gl-form-checkbox v-model="model.runUntagged" data-testid="runner-field-run-untagged">
- {{ __('Run untagged jobs') }}
- <template #help>
- {{ s__('Runners|Use the runner for jobs without tags, in addition to tagged jobs.') }}
- </template>
- </gl-form-checkbox>
+ <hr />
- <gl-form-checkbox
- v-if="canBeLockedToProject"
- v-model="model.locked"
- data-testid="runner-field-locked"
- >
- {{ __('Lock to current projects') }}
- <template #help>
- {{
- s__(
- 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.',
- )
- }}
- </template>
- </gl-form-checkbox>
+ <h4 class="gl-font-lg gl-my-5">{{ s__('Runners|Configuration') }}</h4>
- <gl-form-group :label="__('IP Address')" data-testid="runner-field-ip-address">
- <gl-form-input-group :value="readonlyIpAddress" readonly select-on-click>
- <template #append>
- <gl-button
- v-gl-tooltip.hover
- :title="__('Copy IP Address')"
- :aria-label="__('Copy IP Address')"
- :data-clipboard-text="readonlyIpAddress"
- icon="copy-to-clipboard"
- class="d-inline-flex"
- />
- </template>
- </gl-form-input-group>
- </gl-form-group>
+ <template v-if="loading">
+ <gl-skeleton-loader v-for="i in 3" :key="i" />
+ </template>
+ <template v-else>
+ <div class="gl-mb-5">
+ <gl-form-checkbox
+ v-model="model.active"
+ data-testid="runner-field-paused"
+ :value="false"
+ :unchecked-value="true"
+ >
+ {{ __('Paused') }}
+ <template #help>
+ {{ s__('Runners|Stop the runner from accepting new jobs.') }}
+ </template>
+ </gl-form-checkbox>
- <gl-form-group :label="__('Description')" data-testid="runner-field-description">
- <gl-form-input-group v-model="model.description" />
- </gl-form-group>
+ <gl-form-checkbox
+ v-model="model.accessLevel"
+ data-testid="runner-field-protected"
+ :value="$options.ACCESS_LEVEL_REF_PROTECTED"
+ :unchecked-value="$options.ACCESS_LEVEL_NOT_PROTECTED"
+ >
+ {{ __('Protected') }}
+ <template #help>
+ {{ s__('Runners|Use the runner on pipelines for protected branches only.') }}
+ </template>
+ </gl-form-checkbox>
- <gl-form-group
- data-testid="runner-field-max-timeout"
- :label="__('Maximum job timeout')"
- :description="
- s__(
- 'Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project.',
- )
- "
- >
- <gl-form-input-group v-model.number="model.maximumTimeout" type="number" />
- </gl-form-group>
+ <gl-form-checkbox v-model="model.runUntagged" data-testid="runner-field-run-untagged">
+ {{ __('Run untagged jobs') }}
+ <template #help>
+ {{ s__('Runners|Use the runner for jobs without tags, in addition to tagged jobs.') }}
+ </template>
+ </gl-form-checkbox>
- <gl-form-group
- data-testid="runner-field-tags"
- :label="__('Tags')"
- :description="
- __('You can set up jobs to only use runners with specific tags. Separate tags with commas.')
- "
- >
- <gl-form-input-group v-model="model.tagList" />
- </gl-form-group>
+ <gl-form-checkbox
+ v-if="canBeLockedToProject"
+ v-model="model.locked"
+ data-testid="runner-field-locked"
+ >
+ {{ __('Lock to current projects') }} <gl-icon name="lock" />
+ <template #help>
+ {{
+ s__(
+ 'Runners|Use the runner for the currently assigned projects only. Only administrators can change the assigned projects.',
+ )
+ }}
+ </template>
+ </gl-form-checkbox>
+ </div>
- <runner-update-cost-factor-fields v-model="model" />
+ <gl-form-group
+ data-testid="runner-field-max-timeout"
+ :label="__('Maximum job timeout')"
+ :description="
+ s__(
+ 'Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project.',
+ )
+ "
+ >
+ <gl-form-input-group v-model.number="model.maximumTimeout" type="number" />
+ </gl-form-group>
+
+ <gl-form-group
+ data-testid="runner-field-tags"
+ :label="__('Tags')"
+ :description="
+ __(
+ 'You can set up jobs to only use runners with specific tags. Separate tags with commas.',
+ )
+ "
+ >
+ <gl-form-input-group v-model="model.tagList" />
+ </gl-form-group>
+
+ <runner-update-cost-factor-fields v-model="model" />
+ </template>
- <div class="form-actions">
+ <div class="gl-mt-6">
<gl-button
type="submit"
variant="confirm"
class="js-no-auto-disable"
- :loading="saving || !runner"
+ :loading="loading || saving"
>
{{ __('Save changes') }}
</gl-button>
+ <gl-button :href="runnerPath">
+ {{ __('Cancel') }}
+ </gl-button>
</div>
</gl-form>
</template>
diff --git a/app/assets/javascripts/runner/graphql/details/runner.query.graphql b/app/assets/javascripts/runner/graphql/details/runner.query.graphql
deleted file mode 100644
index 4792a186160..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner.query.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
-
-query getRunner($id: CiRunnerID!) {
- # We have an id in deeply nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
- runner(id: $id) {
- ...RunnerDetails
- }
-}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql
deleted file mode 100644
index 2449ee0fc0f..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner_details.fragment.graphql
+++ /dev/null
@@ -1,5 +0,0 @@
-#import "./runner_details_shared.fragment.graphql"
-
-fragment RunnerDetails on CiRunner {
- ...RunnerDetailsShared
-}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql
deleted file mode 100644
index d8c67728fac..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner_details_shared.fragment.graphql
+++ /dev/null
@@ -1,35 +0,0 @@
-fragment RunnerDetailsShared on CiRunner {
- __typename
- id
- runnerType
- active
- accessLevel
- runUntagged
- locked
- ipAddress
- description
- maximumTimeout
- jobCount
- tagList
- createdAt
- status(legacyMode: null)
- contactedAt
- version
- editAdminUrl
- userPermissions {
- updateRunner
- deleteRunner
- }
- groups {
- # Only a single group can be loaded here, while projects
- # are loaded separately using the query with pagination
- # parameters `runner_projects.query.graphql`.
- nodes {
- id
- avatarUrl
- name
- fullName
- webUrl
- }
- }
-}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql
deleted file mode 100644
index e4bf51e2c30..00000000000
--- a/app/assets/javascripts/runner/graphql/details/runner_update.mutation.graphql
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "ee_else_ce/runner/graphql/details/runner_details.fragment.graphql"
-
-# Mutation for updates from the runner form, loads
-# attributes shown in the runner details.
-
-mutation runnerUpdate($input: RunnerUpdateInput!) {
- runnerUpdate(input: $input) {
- # We have an id in deep nested fragment
- # eslint-disable-next-line @graphql-eslint/require-id-when-available
- runner {
- ...RunnerDetails
- }
- errors
- }
-}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql
new file mode 100644
index 00000000000..b732d587d70
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_fields.fragment.graphql
@@ -0,0 +1,5 @@
+#import "./runner_fields_shared.fragment.graphql"
+
+fragment RunnerFields on CiRunner {
+ ...RunnerFieldsShared
+}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
new file mode 100644
index 00000000000..f900a0450e5
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
@@ -0,0 +1,15 @@
+fragment RunnerFieldsShared on CiRunner {
+ __typename
+ id
+ shortSha
+ runnerType
+ active
+ accessLevel
+ runUntagged
+ locked
+ description
+ maximumTimeout
+ tagList
+ createdAt
+ status(legacyMode: null)
+}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql
new file mode 100644
index 00000000000..0bf66c223fc
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_form.query.graphql
@@ -0,0 +1,7 @@
+#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
+
+query getRunnerForm($id: CiRunnerID!) {
+ runner(id: $id) {
+ ...RunnerFields
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql
new file mode 100644
index 00000000000..8694a51b5a4
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/edit/runner_update.mutation.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/runner/graphql/edit/runner_fields.fragment.graphql"
+
+# Mutation for updates from the runner form, loads
+# attributes shown in the runner details.
+
+mutation runnerUpdate($input: RunnerUpdateInput!) {
+ runnerUpdate(input: $input) {
+ runner {
+ ...RunnerFields
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/show/runner.query.graphql b/app/assets/javascripts/runner/graphql/show/runner.query.graphql
new file mode 100644
index 00000000000..178816b58bd
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/show/runner.query.graphql
@@ -0,0 +1,41 @@
+query getRunner($id: CiRunnerID!) {
+ runner(id: $id) {
+ __typename
+ id
+ shortSha
+ runnerType
+ active
+ accessLevel
+ runUntagged
+ locked
+ ipAddress
+ executorName
+ architectureName
+ platformName
+ description
+ maximumTimeout
+ jobCount
+ tagList
+ createdAt
+ status(legacyMode: null)
+ contactedAt
+ version
+ editAdminUrl
+ userPermissions {
+ updateRunner
+ deleteRunner
+ }
+ groups {
+ # Only a single group can be loaded here, while projects
+ # are loaded separately using the query with pagination
+ # parameters `runner_projects.query.graphql`.
+ nodes {
+ id
+ avatarUrl
+ name
+ fullName
+ webUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql
index 14585e62bf2..14585e62bf2 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_jobs.query.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql
diff --git a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
index cb27de7c200..cb27de7c200 100644
--- a/app/assets/javascripts/runner/graphql/details/runner_projects.query.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index b299d7c40fe..b5bd4b111fd 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -34,7 +34,7 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
-const runnersCountSmartQuery = {
+const countSmartQuery = () => ({
query: groupRunnersCountQuery,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
update(data) {
@@ -43,6 +43,39 @@ const runnersCountSmartQuery = {
error(error) {
this.reportToSentry(error);
},
+});
+
+const tabCountSmartQuery = ({ type }) => {
+ return {
+ ...countSmartQuery(),
+ variables() {
+ return {
+ ...this.countVariables,
+ type,
+ };
+ },
+ };
+};
+
+const statusCountSmartQuery = ({ status, name }) => {
+ return {
+ ...countSmartQuery(),
+ skip() {
+ // skip if filtering by status and not using _this_ status as filter
+ if (this.countVariables.status && this.countVariables.status !== status) {
+ // reset count for given status
+ this[name] = null;
+ return true;
+ }
+ return false;
+ },
+ variables() {
+ return {
+ ...this.countVariables,
+ status,
+ };
+ },
+ };
};
export default {
@@ -116,59 +149,27 @@ export default {
this.reportToSentry(error);
},
},
- onlineRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- groupFullPath: this.groupFullPath,
- status: STATUS_ONLINE,
- };
- },
- },
- offlineRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- groupFullPath: this.groupFullPath,
- status: STATUS_OFFLINE,
- };
- },
- },
- staleRunnersTotal: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- groupFullPath: this.groupFullPath,
- status: STATUS_STALE,
- };
- },
- },
+
+ // Tabs counts
allRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: null,
- };
- },
+ ...tabCountSmartQuery({ type: null }),
},
groupRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: GROUP_TYPE,
- };
- },
+ ...tabCountSmartQuery({ type: GROUP_TYPE }),
},
projectRunnersCount: {
- ...runnersCountSmartQuery,
- variables() {
- return {
- ...this.countVariables,
- type: PROJECT_TYPE,
- };
- },
+ ...tabCountSmartQuery({ type: PROJECT_TYPE }),
+ },
+
+ // Runner status summary
+ onlineRunnersTotal: {
+ ...statusCountSmartQuery({ status: STATUS_ONLINE, name: 'onlineRunnersTotal' }),
+ },
+ offlineRunnersTotal: {
+ ...statusCountSmartQuery({ status: STATUS_OFFLINE, name: 'offlineRunnersTotal' }),
+ },
+ staleRunnersTotal: {
+ ...statusCountSmartQuery({ status: STATUS_STALE, name: 'staleRunnersTotal' }),
},
},
computed: {
@@ -263,12 +264,6 @@ export default {
<template>
<div>
- <runner-stats
- :online-runners-count="onlineRunnersTotal"
- :offline-runners-count="offlineRunnersTotal"
- :stale-runners-count="staleRunnersTotal"
- />
-
<div class="gl-display-flex gl-align-items-center">
<runner-type-tabs
v-model="search"
@@ -298,6 +293,12 @@ export default {
:namespace="filteredSearchNamespace"
/>
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
+
<div v-if="noRunnersFound" class="gl-text-center gl-p-5">
{{ __('No runners found') }}
</div>
diff --git a/app/assets/javascripts/runner/local_storage_alert/constants.js b/app/assets/javascripts/runner/local_storage_alert/constants.js
new file mode 100644
index 00000000000..69b7418f898
--- /dev/null
+++ b/app/assets/javascripts/runner/local_storage_alert/constants.js
@@ -0,0 +1 @@
+export const LOCAL_STORAGE_ALERT_KEY = 'local-storage-alert';
diff --git a/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js b/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js
new file mode 100644
index 00000000000..ca7c627459a
--- /dev/null
+++ b/app/assets/javascripts/runner/local_storage_alert/save_alert_to_local_storage.js
@@ -0,0 +1,8 @@
+import AccessorUtilities from '~/lib/utils/accessor';
+import { LOCAL_STORAGE_ALERT_KEY } from './constants';
+
+export const saveAlertToLocalStorage = (alertOptions) => {
+ if (AccessorUtilities.canUseLocalStorage()) {
+ localStorage.setItem(LOCAL_STORAGE_ALERT_KEY, JSON.stringify(alertOptions));
+ }
+};
diff --git a/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js b/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js
new file mode 100644
index 00000000000..d768a06494a
--- /dev/null
+++ b/app/assets/javascripts/runner/local_storage_alert/show_alert_from_local_storage.js
@@ -0,0 +1,18 @@
+import AccessorUtilities from '~/lib/utils/accessor';
+import { LOCAL_STORAGE_ALERT_KEY } from './constants';
+
+export const showAlertFromLocalStorage = async () => {
+ if (AccessorUtilities.canUseLocalStorage()) {
+ const alertOptions = localStorage.getItem(LOCAL_STORAGE_ALERT_KEY);
+
+ if (alertOptions) {
+ try {
+ const { createAlert } = await import('~/flash');
+ createAlert(JSON.parse(alertOptions));
+ } catch {
+ // ignore when the alert data cannot be parsed
+ }
+ }
+ localStorage.removeItem(LOCAL_STORAGE_ALERT_KEY);
+ }
+};
diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js
index 5e3c412ddb6..0d688ed65ef 100644
--- a/app/assets/javascripts/runner/runner_search_utils.js
+++ b/app/assets/javascripts/runner/runner_search_utils.js
@@ -18,7 +18,6 @@ import {
PARAM_KEY_BEFORE,
DEFAULT_SORT,
RUNNER_PAGE_SIZE,
- STATUS_NEVER_CONTACTED,
} from './constants';
import { getPaginationVariables } from './utils';
@@ -84,7 +83,6 @@ const getPaginationFromParams = (params) => {
};
// Outdated URL parameters
-const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
const STATUS_ACTIVE = 'ACTIVE';
const STATUS_PAUSED = 'PAUSED';
@@ -116,10 +114,6 @@ export const updateOutdatedUrl = (url = window.location.href) => {
const status = params[PARAM_KEY_STATUS]?.[0] || null;
switch (status) {
- case STATUS_NOT_CONNECTED:
- return updateUrlParams(url, {
- [PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED],
- });
case STATUS_ACTIVE:
return updateUrlParams(url, {
[PARAM_KEY_PAUSED]: ['false'],
diff --git a/app/assets/javascripts/runner/utils.js b/app/assets/javascripts/runner/utils.js
index 1f7794720de..cb2917a92fd 100644
--- a/app/assets/javascripts/runner/utils.js
+++ b/app/assets/javascripts/runner/utils.js
@@ -1,5 +1,4 @@
import { formatNumber } from '~/locale';
-import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants';
import { RUNNER_JOB_COUNT_LIMIT } from './constants';
/**
@@ -28,7 +27,7 @@ export const tableField = ({ key, label = '', thClasses = [], ...options }) => {
return {
key,
label,
- thClass: [DEFAULT_TH_CLASSES, ...thClasses],
+ thClass: thClasses,
tdAttr: {
'data-testid': `td-${key}`,
},
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index ba0120a0a70..d0c4ad3646c 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -1,15 +1,15 @@
<script>
import { GlTab, GlTabs, GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
import { __, s__ } from '~/locale';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
+import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
+import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
-import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
+import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, LICENSE_ULTIMATE } from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
-import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
@@ -50,8 +50,18 @@ export default {
UserCalloutDismisser,
TrainingProviderList,
},
- mixins: [glFeatureFlagsMixin()],
inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
+ apollo: {
+ currentLicensePlan: {
+ query: currentLicenseQuery,
+ update({ currentLicense }) {
+ return currentLicense?.plan;
+ },
+ error() {
+ this.hasCurrentLicenseFetchError = true;
+ },
+ },
+ },
props: {
augmentedSecurityFeatures: {
type: Array,
@@ -91,6 +101,8 @@ export default {
return {
autoDevopsEnabledAlertDismissedProjects: [],
errorMessage: '',
+ currentLicensePlan: '',
+ hasCurrentLicenseFetchError: false,
};
},
computed: {
@@ -111,6 +123,12 @@ export default {
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath)
);
},
+ shouldShowVulnerabilityManagementTab() {
+ // if the query fails (if the plan is `null` also means an error has occurred) we still want to show the feature
+ const hasQueryError = this.hasCurrentLicenseFetchError || this.currentLicensePlan === null;
+
+ return hasQueryError || this.currentLicensePlan === LICENSE_ULTIMATE;
+ },
},
methods: {
dismissAutoDevopsEnabledAlert() {
@@ -175,7 +193,7 @@ export default {
@dismiss="dismissAutoDevopsEnabledAlert"
/>
- <section-layout :heading="$options.i18n.securityTesting">
+ <section-layout class="gl-border-b-0" :heading="$options.i18n.securityTesting">
<template #description>
<p>
<span data-testid="latest-pipeline-info-security">
@@ -252,7 +270,7 @@ export default {
</section-layout>
</gl-tab>
<gl-tab
- v-if="glFeatures.secureVulnerabilityTraining"
+ v-if="shouldShowVulnerabilityManagementTab"
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
query-param-value="vulnerability-management"
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 6db28ef0fad..5b04ad6f9ba 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -55,7 +55,7 @@ export const DAST_DESCRIPTION = s__(
);
export const DAST_HELP_PATH = helpPagePath('user/application_security/dast/index');
export const DAST_CONFIG_HELP_PATH = helpPagePath('user/application_security/dast/index', {
- anchor: 'enable-dast',
+ anchor: 'enable-automatic-dast-run',
});
export const DAST_BADGE_TEXT = __('Available on-demand');
export const DAST_BADGE_TOOLTIP = __(
@@ -126,7 +126,7 @@ export const COVERAGE_FUZZING_HELP_PATH = helpPagePath(
);
export const COVERAGE_FUZZING_CONFIG_HELP_PATH = helpPagePath(
'user/application_security/coverage_fuzzing/index',
- { anchor: 'configuration' },
+ { anchor: 'enable-coverage-guided-fuzz-testing' },
);
export const CORPUS_MANAGEMENT_NAME = __('Corpus Management');
@@ -310,3 +310,7 @@ export const TEMP_PROVIDER_URLS = {
Kontra: 'https://application.security/',
[__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
};
+
+export const LICENSE_ULTIMATE = 'ultimate';
+export const LICENSE_FREE = 'free';
+export const LICENSE_PREMIUM = 'premium';
diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue
index 309e5f21445..19b412d66ca 100644
--- a/app/assets/javascripts/security_configuration/components/feature_card.vue
+++ b/app/assets/javascripts/security_configuration/components/feature_card.vue
@@ -119,8 +119,10 @@ export default {
/>
<template v-if="enabled">
- <gl-icon name="check-circle-filled" />
- <span class="gl-text-green-700">{{ $options.i18n.enabled }}</span>
+ <span>
+ <gl-icon name="check-circle-filled" />
+ <span class="gl-text-green-700">{{ $options.i18n.enabled }}</span>
+ </span>
</template>
<template v-else-if="available">
diff --git a/app/assets/javascripts/security_configuration/components/section_layout.vue b/app/assets/javascripts/security_configuration/components/section_layout.vue
deleted file mode 100644
index 1fe8dd862a0..00000000000
--- a/app/assets/javascripts/security_configuration/components/section_layout.vue
+++ /dev/null
@@ -1,23 +0,0 @@
-<script>
-export default {
- name: 'SectionLayout',
- props: {
- heading: {
- type: String,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="row gl-line-height-20 gl-pt-6">
- <div class="col-lg-4">
- <h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2>
- <slot name="description"></slot>
- </div>
- <div class="col-lg-8">
- <slot name="features"></slot>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
new file mode 100644
index 00000000000..9ab4f4d4347
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
@@ -0,0 +1,6 @@
+query getCurrentLicensePlan {
+ currentLicense {
+ id
+ plan
+ }
+}
diff --git a/app/assets/javascripts/security_configuration/graphql/security_training_vulnerability.query.graphql b/app/assets/javascripts/security_configuration/graphql/security_training_vulnerability.query.graphql
index f0474614dab..891e0dda312 100644
--- a/app/assets/javascripts/security_configuration/graphql/security_training_vulnerability.query.graphql
+++ b/app/assets/javascripts/security_configuration/graphql/security_training_vulnerability.query.graphql
@@ -5,6 +5,7 @@ query getSecurityTrainingUrls($projectFullPath: ID!, $identifierExternalIds: [St
name
status
url
+ identifier
}
}
}
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 65cf1ec27a3..dcc41a38067 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -37,6 +37,7 @@ export const initSecurityConfiguration = (el) => {
return new Vue({
el,
apolloProvider,
+ name: 'SecurityConfigurationRoot',
provide: {
projectFullPath,
upgradePath,
diff --git a/app/assets/javascripts/serverless/components/area.vue b/app/assets/javascripts/serverless/components/area.vue
deleted file mode 100644
index a9584c070fe..00000000000
--- a/app/assets/javascripts/serverless/components/area.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<script>
-import { GlAreaChart } from '@gitlab/ui/dist/charts';
-import dateFormat from 'dateformat';
-import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
-import { __ } from '~/locale';
-import { X_INTERVAL } from '../constants';
-import { validateGraphData } from '../utils';
-
-let debouncedResize;
-
-export default {
- components: {
- GlAreaChart,
- },
- inheritAttrs: false,
- props: {
- graphData: {
- type: Object,
- required: true,
- validator: validateGraphData,
- },
- containerWidth: {
- type: Number,
- required: true,
- },
- },
- data() {
- return {
- tooltipPopoverTitle: '',
- tooltipPopoverContent: '',
- width: this.containerWidth,
- };
- },
- computed: {
- chartData() {
- return this.graphData.queries.reduce((accumulator, query) => {
- accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []);
- return accumulator;
- }, {});
- },
- extractTimeData() {
- return this.chartData.requests.map((data) => data.time);
- },
- generateSeries() {
- return {
- name: __('Invocations'),
- type: 'line',
- data: this.chartData.requests.map((data) => [data.time, data.value]),
- symbolSize: 0,
- };
- },
- getInterval() {
- const { result } = this.graphData.queries[0];
-
- if (result.length === 0) {
- return 1;
- }
-
- const split = result[0].values.reduce(
- (acc, pair) => (pair.value > acc ? pair.value : acc),
- 1,
- );
-
- return split < X_INTERVAL ? split : X_INTERVAL;
- },
- chartOptions() {
- return {
- xAxis: {
- name: 'time',
- type: 'time',
- axisLabel: {
- formatter: (date) => dateFormat(date, 'h:MM TT'),
- },
- data: this.extractTimeData,
- nameTextStyle: {
- padding: [18, 0, 0, 0],
- },
- },
- yAxis: {
- name: this.yAxisLabel,
- nameTextStyle: {
- padding: [0, 0, 36, 0],
- },
- splitNumber: this.getInterval,
- },
- legend: {
- formatter: this.xAxisLabel,
- },
- series: this.generateSeries,
- };
- },
- xAxisLabel() {
- return this.graphData.queries.map((query) => query.label).join(', ');
- },
- yAxisLabel() {
- const [query] = this.graphData.queries;
- return `${this.graphData.y_label} (${query.unit})`;
- },
- },
- watch: {
- containerWidth: 'onResize',
- },
- beforeDestroy() {
- window.removeEventListener('resize', debouncedResize);
- },
- created() {
- debouncedResize = debounceByAnimationFrame(this.onResize);
- window.addEventListener('resize', debouncedResize);
- },
- methods: {
- formatTooltipText(params) {
- const [seriesData] = params.seriesData;
- this.tooltipPopoverTitle = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
- this.tooltipPopoverContent = `${this.yAxisLabel}: ${seriesData.value[1]}`;
- },
- onResize() {
- const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
- this.width = width;
- },
- },
-};
-</script>
-
-<template>
- <div class="prometheus-graph">
- <div class="prometheus-graph-header">
- <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div ref="graphWidgets" class="prometheus-graph-widgets">
- <slot></slot>
- </div>
- </div>
- <gl-area-chart
- ref="areaChart"
- v-bind="$attrs"
- :data="[]"
- :option="chartOptions"
- :format-tooltip-text="formatTooltipText"
- :width="width"
- :include-legend-avg-max="false"
- >
- <template #tooltip-title>{{ tooltipPopoverTitle }}</template>
- <template #tooltip-content>{{ tooltipPopoverContent }}</template>
- </gl-area-chart>
- </div>
-</template>
diff --git a/app/assets/javascripts/serverless/components/empty_state.vue b/app/assets/javascripts/serverless/components/empty_state.vue
deleted file mode 100644
index 6d1cea519c4..00000000000
--- a/app/assets/javascripts/serverless/components/empty_state.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-<script>
-import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
-import { mapState } from 'vuex';
-import { s__ } from '~/locale';
-import { DEPRECATION_POST_LINK } from '../constants';
-
-export default {
- components: {
- GlEmptyState,
- GlLink,
- GlSprintf,
- },
- i18n: {
- title: s__('Serverless|Getting started with serverless'),
- description: s__(
- 'Serverless|Serverless was %{postLinkStart}deprecated%{postLinkEnd}. But if you opt to use it, you must install Knative in your Kubernetes cluster first. %{linkStart}Learn more.%{linkEnd}',
- ),
- },
- deprecationPostLink: DEPRECATION_POST_LINK,
- computed: {
- ...mapState(['emptyImagePath', 'helpPath']),
- },
-};
-</script>
-
-<template>
- <gl-empty-state :svg-path="emptyImagePath" :title="$options.i18n.title">
- <template #description>
- <gl-sprintf :message="$options.i18n.description">
- <template #postLink="{ content }">
- <gl-link :href="$options.deprecationPostLink" target="_blank">{{ content }}</gl-link>
- </template>
- <template #link="{ content }">
- <gl-link :href="helpPath">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </template>
- </gl-empty-state>
-</template>
diff --git a/app/assets/javascripts/serverless/components/environment_row.vue b/app/assets/javascripts/serverless/components/environment_row.vue
deleted file mode 100644
index 01030172ea8..00000000000
--- a/app/assets/javascripts/serverless/components/environment_row.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import ItemCaret from '~/groups/components/item_caret.vue';
-import FunctionRow from './function_row.vue';
-
-export default {
- components: {
- ItemCaret,
- FunctionRow,
- },
- props: {
- env: {
- type: Array,
- required: true,
- },
- envName: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- isOpen: true,
- };
- },
- computed: {
- envId() {
- if (this.envName === '*') {
- return 'env-global';
- }
-
- return `env-${this.envName}`;
- },
- isOpenClass() {
- return {
- 'is-open': this.isOpen,
- };
- },
- },
- methods: {
- toggleOpen() {
- this.isOpen = !this.isOpen;
- },
- },
-};
-</script>
-
-<template>
- <li :id="envId" :class="isOpenClass" class="group-row has-children">
- <div
- class="group-row-contents d-flex justify-content-end align-items-center py-2"
- role="button"
- @click.stop="toggleOpen"
- >
- <div class="folder-toggle-wrap d-flex align-items-center">
- <item-caret :is-group-open="isOpen" />
- </div>
- <div class="group-text flex-grow title namespace-title gl-ml-3">
- {{ envName }}
- </div>
- </div>
- <ul v-if="isOpen" class="content-list group-list-tree">
- <function-row v-for="(f, index) in env" :key="f.name" :index="index" :func="f" />
- </ul>
- </li>
-</template>
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
deleted file mode 100644
index d2306c2d8bd..00000000000
--- a/app/assets/javascripts/serverless/components/function_details.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-<script>
-import { isString } from 'lodash';
-import { mapState, mapActions, mapGetters } from 'vuex';
-import AreaChart from './area.vue';
-import MissingPrometheus from './missing_prometheus.vue';
-import PodBox from './pod_box.vue';
-import Url from './url.vue';
-
-export default {
- components: {
- PodBox,
- Url,
- AreaChart,
- MissingPrometheus,
- },
- props: {
- func: {
- type: Object,
- required: true,
- },
- hasPrometheus: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- return {
- elWidth: 0,
- };
- },
- computed: {
- name() {
- return this.func.name;
- },
- description() {
- return isString(this.func.description) ? this.func.description : '';
- },
- funcUrl() {
- return this.func.url;
- },
- podCount() {
- return Number(this.func.podcount) || 0;
- },
- ...mapState(['graphData', 'hasPrometheusData']),
- ...mapGetters(['hasPrometheusMissingData']),
- },
- created() {
- this.fetchMetrics({
- metricsPath: this.func.metricsUrl,
- hasPrometheus: this.hasPrometheus,
- });
- },
- mounted() {
- this.elWidth = this.$el.clientWidth;
- },
- methods: {
- ...mapActions(['fetchMetrics']),
- },
-};
-</script>
-
-<template>
- <section id="serverless-function-details">
- <h3 class="serverless-function-name">{{ name }}</h3>
- <div class="gl-mb-3 serverless-function-description">
- <div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div>
- </div>
- <url :uri="funcUrl" />
-
- <h4>{{ s__('ServerlessDetails|Kubernetes Pods') }}</h4>
- <div v-if="podCount > 0">
- <p>
- <b v-if="podCount == 1">{{ podCount }} {{ s__('ServerlessDetails|pod in use') }}</b>
- <b v-else>{{ podCount }} {{ s__('ServerlessDetails|pods in use') }}</b>
- </p>
- <pod-box :count="podCount" />
- <p>
- {{
- s__('ServerlessDetails|Number of Kubernetes pods in use over time based on necessity.')
- }}
- </p>
- </div>
- <div v-else>
- <p>{{ s__('ServerlessDetails|No pods loaded at this time.') }}</p>
- </div>
-
- <area-chart v-if="hasPrometheusData" :graph-data="graphData" :container-width="elWidth" />
- <missing-prometheus
- v-if="!hasPrometheus || hasPrometheusMissingData"
- :missing-data="hasPrometheusMissingData"
- />
- </section>
-</template>
diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue
deleted file mode 100644
index fab9c0a75e7..00000000000
--- a/app/assets/javascripts/serverless/components/function_row.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<script>
-import { isString } from 'lodash';
-import { visitUrl } from '~/lib/utils/url_utility';
-import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
-import Url from './url.vue';
-
-export default {
- components: {
- Timeago,
- Url,
- },
- props: {
- func: {
- type: Object,
- required: true,
- },
- },
- computed: {
- name() {
- return this.func.name;
- },
- description() {
- if (!isString(this.func.description)) {
- return '';
- }
-
- const desc = this.func.description.split('\n');
- if (desc.length > 1) {
- return desc[1];
- }
-
- return desc[0];
- },
- detailUrl() {
- return this.func.detail_url;
- },
- targetUrl() {
- return this.func.url;
- },
- image() {
- return this.func.image;
- },
- timestamp() {
- return this.func.created_at;
- },
- },
- methods: {
- checkClass(element) {
- if (element.closest('.no-expand') === null) {
- return true;
- }
-
- return false;
- },
- openDetails(e) {
- if (this.checkClass(e.target)) {
- visitUrl(this.detailUrl);
- }
- },
- },
-};
-</script>
-
-<template>
- <li :id="name" class="group-row">
- <div class="group-row-contents py-2" role="button" @click="openDetails">
- <p class="float-right text-right">
- <span>{{ image }}</span
- ><br />
- <timeago :time="timestamp" />
- </p>
- <b>{{ name }}</b>
- <div v-for="line in description.split('\n')" :key="line">{{ line }}</div>
- <url :uri="targetUrl" class="gl-mt-3 no-expand" />
- </div>
- </li>
-</template>
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
deleted file mode 100644
index e9461aa3ead..00000000000
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ /dev/null
@@ -1,139 +0,0 @@
-<script>
-import {
- GlLink,
- GlAlert,
- GlSprintf,
- GlLoadingIcon,
- GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
-import { mapState, mapActions, mapGetters } from 'vuex';
-import { sprintf, s__ } from '~/locale';
-import { CHECKING_INSTALLED, DEPRECATION_POST_LINK } from '../constants';
-import EmptyState from './empty_state.vue';
-import EnvironmentRow from './environment_row.vue';
-
-export default {
- components: {
- EnvironmentRow,
- EmptyState,
- GlLink,
- GlAlert,
- GlSprintf,
- GlLoadingIcon,
- },
- directives: {
- SafeHtml,
- },
- deprecationPostLink: DEPRECATION_POST_LINK,
- computed: {
- ...mapState(['installed', 'isLoading', 'hasFunctionData', 'helpPath', 'statusPath']),
- ...mapGetters(['getFunctions']),
-
- checkingInstalled() {
- return this.installed === CHECKING_INSTALLED;
- },
- isInstalled() {
- return this.installed === true;
- },
- noServerlessConfigFile() {
- return sprintf(
- s__(
- 'Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file.',
- ),
- { startTag: '<code>', endTag: '</code>' },
- false,
- );
- },
- noGitlabYamlConfigured() {
- return sprintf(
- s__('Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured.'),
- { startTag: '<code>', endTag: '</code>' },
- false,
- );
- },
- mismatchedServerlessFunctions() {
- return sprintf(
- s__(
- "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster.",
- ),
- { startTag: '<code>', endTag: '</code>' },
- false,
- );
- },
- },
- created() {
- this.fetchFunctions({
- functionsPath: this.statusPath,
- });
- },
- methods: {
- ...mapActions(['fetchFunctions']),
- },
-};
-</script>
-
-<template>
- <section id="serverless-functions" class="flex-grow">
- <gl-alert class="gl-mt-6" variant="warning" :dismissible="false">
- <gl-sprintf
- :message="s__('Serverless|Serverless was %{linkStart}deprecated%{linkEnd} in GitLab 14.3.')"
- ><template #link="{ content }"
- ><gl-link :href="$options.deprecationPostLink" target="_blank">{{
- content
- }}</gl-link></template
- ></gl-sprintf
- >
- </gl-alert>
-
- <gl-loading-icon v-if="checkingInstalled" size="lg" class="gl-mt-3 gl-mb-3" />
-
- <div v-else-if="isInstalled">
- <div v-if="hasFunctionData">
- <div class="groups-list-tree-container js-functions-wrapper">
- <ul class="content-list group-list-tree">
- <environment-row
- v-for="(env, index) in getFunctions"
- :key="index"
- :env="env"
- :env-name="index"
- />
- </ul>
- </div>
- <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3 js-functions-loader" />
- </div>
- <div v-else class="empty-state js-empty-state">
- <div class="text-content">
- <h4 class="state-title text-center">{{ s__('Serverless|No functions available') }}</h4>
- <p class="state-description">
- {{
- s__(
- 'Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:',
- )
- }}
- </p>
- <ul>
- <li v-safe-html="noServerlessConfigFile"></li>
- <li v-safe-html="noGitlabYamlConfigured"></li>
- <li v-safe-html="mismatchedServerlessFunctions"></li>
- <li>{{ s__('Serverless|The deploy job has not finished.') }}</li>
- </ul>
-
- <p>
- {{
- s__(
- 'Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available.',
- )
- }}
- </p>
- <div class="text-center">
- <gl-link :href="helpPath" class="btn btn-success">{{
- s__('Serverless|Learn more about Serverless')
- }}</gl-link>
- </div>
- </div>
- </div>
- </div>
-
- <empty-state v-else />
- </section>
-</template>
diff --git a/app/assets/javascripts/serverless/components/missing_prometheus.vue b/app/assets/javascripts/serverless/components/missing_prometheus.vue
deleted file mode 100644
index d9e6bb5009e..00000000000
--- a/app/assets/javascripts/serverless/components/missing_prometheus.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script>
-import { GlButton, GlLink } from '@gitlab/ui';
-import { mapState } from 'vuex';
-import { s__ } from '~/locale';
-
-export default {
- components: {
- GlButton,
- GlLink,
- },
- props: {
- missingData: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- ...mapState(['clustersPath', 'helpPath']),
- missingStateClass() {
- return this.missingData ? 'missing-prometheus-state' : 'empty-prometheus-state';
- },
- prometheusHelpPath() {
- return `${this.helpPath}#prometheus-support`;
- },
- description() {
- return this.missingData
- ? s__(`ServerlessDetails|Invocation metrics loading or not available at this time.`)
- : s__(
- `ServerlessDetails|Function invocation metrics require the Prometheus cluster integration.`,
- );
- },
- },
-};
-</script>
-
-<template>
- <div class="row" :class="missingStateClass">
- <div class="col-12">
- <div class="text-content">
- <h4 class="state-title text-left">{{ s__(`ServerlessDetails|Invocations`) }}</h4>
- <p class="state-description">
- {{ description }}
- <gl-link :href="prometheusHelpPath">{{
- s__(`ServerlessDetails|More information`)
- }}</gl-link
- >.
- </p>
-
- <div v-if="!missingData" class="text-left">
- <gl-button :href="clustersPath" variant="success" category="primary">
- {{ s__('ServerlessDetails|Configure cluster.') }}
- </gl-button>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/serverless/components/pod_box.vue b/app/assets/javascripts/serverless/components/pod_box.vue
deleted file mode 100644
index 04d3641bce3..00000000000
--- a/app/assets/javascripts/serverless/components/pod_box.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<script>
-export default {
- props: {
- count: {
- type: Number,
- required: true,
- },
- color: {
- type: String,
- required: false,
- default: 'green',
- },
- },
- methods: {
- boxOffset(i) {
- return 20 * (i - 1);
- },
- },
-};
-</script>
-
-<template>
- <svg :width="boxOffset(count + 1)" :height="20">
- <rect
- v-for="i in count"
- :key="i"
- width="15"
- height="15"
- rx="5"
- ry="5"
- :fill="color"
- :x="boxOffset(i)"
- y="0"
- />
- </svg>
-</template>
diff --git a/app/assets/javascripts/serverless/components/url.vue b/app/assets/javascripts/serverless/components/url.vue
deleted file mode 100644
index b105f49e475..00000000000
--- a/app/assets/javascripts/serverless/components/url.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<script>
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-
-export default {
- components: {
- ClipboardButton,
- },
- props: {
- uri: {
- type: String,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div class="clipboard-group">
- <div class="gl-cursor-text label label-monospace monospace" data-testid="url-text-field">
- {{ uri }}
- </div>
- <clipboard-button
- :text="uri"
- :title="s__('ServerlessURL|Copy URL')"
- class="input-group-text js-clipboard-btn"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/serverless/constants.js b/app/assets/javascripts/serverless/constants.js
deleted file mode 100644
index 42c9ee983b4..00000000000
--- a/app/assets/javascripts/serverless/constants.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const MAX_REQUESTS = 3; // max number of times to retry
-
-export const X_INTERVAL = 5; // Reflects the number of verticle bars on the x-axis
-
-export const CHECKING_INSTALLED = 'checking'; // The backend is still determining whether or not Knative is installed
-
-export const TIMEOUT = 'timeout';
-
-export const DEPRECATION_POST_LINK =
- 'https://about.gitlab.com/releases/2021/09/22/gitlab-14-3-released/#gitlab-serverless';
diff --git a/app/assets/javascripts/serverless/event_hub.js b/app/assets/javascripts/serverless/event_hub.js
deleted file mode 100644
index e31806ad199..00000000000
--- a/app/assets/javascripts/serverless/event_hub.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import createEventHub from '~/helpers/event_hub_factory';
-
-export default createEventHub();
diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js
deleted file mode 100644
index e8d87a40fc7..00000000000
--- a/app/assets/javascripts/serverless/serverless_bundle.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import Vue from 'vue';
-import FunctionDetails from './components/function_details.vue';
-import Functions from './components/functions.vue';
-import { createStore } from './store';
-
-export default class Serverless {
- constructor() {
- if (document.querySelector('.js-serverless-function-details-page') != null) {
- const entryPointData = document.querySelector('.js-serverless-function-details-page').dataset;
- const store = createStore(entryPointData);
-
- const {
- serviceName,
- serviceDescription,
- serviceEnvironment,
- serviceUrl,
- serviceNamespace,
- servicePodcount,
- serviceMetricsUrl,
- prometheus,
- } = entryPointData;
- const el = document.querySelector('#js-serverless-function-details');
-
- const service = {
- name: serviceName,
- description: serviceDescription,
- environment: serviceEnvironment,
- url: serviceUrl,
- namespace: serviceNamespace,
- podcount: servicePodcount,
- metricsUrl: serviceMetricsUrl,
- };
-
- this.functionDetails = new Vue({
- el,
- store,
- render(createElement) {
- return createElement(FunctionDetails, {
- props: {
- func: service,
- hasPrometheus: prometheus !== undefined,
- },
- });
- },
- });
- } else {
- const entryPointData = document.querySelector('.js-serverless-functions-page').dataset;
- const store = createStore(entryPointData);
-
- const el = document.querySelector('#js-serverless-functions');
- this.functions = new Vue({
- el,
- store,
- render(createElement) {
- return createElement(Functions);
- },
- });
- }
- }
-
- destroy() {
- this.destroyed = true;
-
- this.functions.$destroy();
- this.functionDetails.$destroy();
- }
-}
diff --git a/app/assets/javascripts/serverless/store/actions.js b/app/assets/javascripts/serverless/store/actions.js
deleted file mode 100644
index 166cd796680..00000000000
--- a/app/assets/javascripts/serverless/store/actions.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import createFlash from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { backOff } from '~/lib/utils/common_utils';
-import statusCodes from '~/lib/utils/http_status';
-import { __ } from '~/locale';
-import { MAX_REQUESTS, CHECKING_INSTALLED, TIMEOUT } from '../constants';
-import * as types from './mutation_types';
-
-export const requestFunctionsLoading = ({ commit }) => commit(types.REQUEST_FUNCTIONS_LOADING);
-export const receiveFunctionsSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_FUNCTIONS_SUCCESS, data);
-export const receiveFunctionsPartial = ({ commit }, data) =>
- commit(types.RECEIVE_FUNCTIONS_PARTIAL, data);
-export const receiveFunctionsTimeout = ({ commit }, data) =>
- commit(types.RECEIVE_FUNCTIONS_TIMEOUT, data);
-export const receiveFunctionsNoDataSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_FUNCTIONS_NODATA_SUCCESS, data);
-export const receiveFunctionsError = ({ commit }, error) =>
- commit(types.RECEIVE_FUNCTIONS_ERROR, error);
-
-export const receiveMetricsSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_METRICS_SUCCESS, data);
-export const receiveMetricsNoPrometheus = ({ commit }) =>
- commit(types.RECEIVE_METRICS_NO_PROMETHEUS);
-export const receiveMetricsNoDataSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_METRICS_NODATA_SUCCESS, data);
-export const receiveMetricsError = ({ commit }, error) =>
- commit(types.RECEIVE_METRICS_ERROR, error);
-
-export const fetchFunctions = ({ dispatch }, { functionsPath }) => {
- let retryCount = 0;
-
- const functionsPartiallyFetched = (data) => {
- if (data.functions !== null && data.functions.length) {
- dispatch('receiveFunctionsPartial', data);
- }
- };
-
- dispatch('requestFunctionsLoading');
-
- backOff((next, stop) => {
- axios
- .get(functionsPath)
- .then((response) => {
- if (response.data.knative_installed === CHECKING_INSTALLED) {
- retryCount += 1;
- if (retryCount < MAX_REQUESTS) {
- functionsPartiallyFetched(response.data);
- next();
- } else {
- stop(TIMEOUT);
- }
- } else {
- stop(response.data);
- }
- })
- .catch(stop);
- })
- .then((data) => {
- if (data === TIMEOUT) {
- dispatch('receiveFunctionsTimeout');
- createFlash({
- message: __('Loading functions timed out. Please reload the page to try again.'),
- });
- } else if (data.functions !== null && data.functions.length) {
- dispatch('receiveFunctionsSuccess', data);
- } else {
- dispatch('receiveFunctionsNoDataSuccess', data);
- }
- })
- .catch((error) => {
- dispatch('receiveFunctionsError', error);
- createFlash({
- message: error,
- });
- });
-};
-
-export const fetchMetrics = ({ dispatch }, { metricsPath, hasPrometheus }) => {
- let retryCount = 0;
-
- if (!hasPrometheus) {
- dispatch('receiveMetricsNoPrometheus');
- return;
- }
-
- backOff((next, stop) => {
- axios
- .get(metricsPath)
- .then((response) => {
- if (response.status === statusCodes.NO_CONTENT) {
- retryCount += 1;
- if (retryCount < MAX_REQUESTS) {
- next();
- } else {
- dispatch('receiveMetricsNoDataSuccess');
- stop(null);
- }
- } else {
- stop(response.data);
- }
- })
- .catch(stop);
- })
- .then((data) => {
- if (data === null) {
- return;
- }
-
- const updatedMetric = data.metrics;
- const queries = data.metrics.queries.map((query) => ({
- ...query,
- result: query.result.map((result) => ({
- ...result,
- values: result.values.map(([timestamp, value]) => ({
- time: new Date(timestamp * 1000).toISOString(),
- value: Number(value),
- })),
- })),
- }));
-
- updatedMetric.queries = queries;
- dispatch('receiveMetricsSuccess', updatedMetric);
- })
- .catch((error) => {
- dispatch('receiveMetricsError', error);
- createFlash({
- message: error,
- });
- });
-};
diff --git a/app/assets/javascripts/serverless/store/getters.js b/app/assets/javascripts/serverless/store/getters.js
deleted file mode 100644
index da975c56e5d..00000000000
--- a/app/assets/javascripts/serverless/store/getters.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { translate } from '../utils';
-
-export const hasPrometheusMissingData = (state) => state.hasPrometheus && !state.hasPrometheusData;
-
-// Convert the function list into a k/v grouping based on the environment scope
-
-export const getFunctions = (state) => translate(state.functions);
diff --git a/app/assets/javascripts/serverless/store/index.js b/app/assets/javascripts/serverless/store/index.js
deleted file mode 100644
index 6f32d85201e..00000000000
--- a/app/assets/javascripts/serverless/store/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import createState from './state';
-
-Vue.use(Vuex);
-
-export const createStore = (entryPointData = {}) =>
- new Vuex.Store({
- actions,
- getters,
- mutations,
- state: createState(entryPointData),
- });
-
-export default createStore;
diff --git a/app/assets/javascripts/serverless/store/mutation_types.js b/app/assets/javascripts/serverless/store/mutation_types.js
deleted file mode 100644
index b8fa9ea1a01..00000000000
--- a/app/assets/javascripts/serverless/store/mutation_types.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const REQUEST_FUNCTIONS_LOADING = 'REQUEST_FUNCTIONS_LOADING';
-export const RECEIVE_FUNCTIONS_SUCCESS = 'RECEIVE_FUNCTIONS_SUCCESS';
-export const RECEIVE_FUNCTIONS_PARTIAL = 'RECEIVE_FUNCTIONS_PARTIAL';
-export const RECEIVE_FUNCTIONS_TIMEOUT = 'RECEIVE_FUNCTIONS_TIMEOUT';
-export const RECEIVE_FUNCTIONS_NODATA_SUCCESS = 'RECEIVE_FUNCTIONS_NODATA_SUCCESS';
-export const RECEIVE_FUNCTIONS_ERROR = 'RECEIVE_FUNCTIONS_ERROR';
-
-export const RECEIVE_METRICS_NO_PROMETHEUS = 'RECEIVE_METRICS_NO_PROMETHEUS';
-export const RECEIVE_METRICS_SUCCESS = 'RECEIVE_METRICS_SUCCESS';
-export const RECEIVE_METRICS_NODATA_SUCCESS = 'RECEIVE_METRICS_NODATA_SUCCESS';
-export const RECEIVE_METRICS_ERROR = 'RECEIVE_METRICS_ERROR';
diff --git a/app/assets/javascripts/serverless/store/mutations.js b/app/assets/javascripts/serverless/store/mutations.js
deleted file mode 100644
index 2685a5b11ff..00000000000
--- a/app/assets/javascripts/serverless/store/mutations.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as types from './mutation_types';
-
-export default {
- [types.REQUEST_FUNCTIONS_LOADING](state) {
- state.isLoading = true;
- },
- [types.RECEIVE_FUNCTIONS_SUCCESS](state, data) {
- state.functions = data.functions;
- state.installed = data.knative_installed;
- state.isLoading = false;
- state.hasFunctionData = true;
- },
- [types.RECEIVE_FUNCTIONS_PARTIAL](state, data) {
- state.functions = data.functions;
- state.installed = true;
- state.isLoading = true;
- state.hasFunctionData = true;
- },
- [types.RECEIVE_FUNCTIONS_TIMEOUT](state) {
- state.isLoading = false;
- },
- [types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, data) {
- state.isLoading = false;
- state.installed = data.knative_installed;
- state.hasFunctionData = false;
- },
- [types.RECEIVE_FUNCTIONS_ERROR](state, error) {
- state.error = error;
- state.hasFunctionData = false;
- state.isLoading = false;
- },
- [types.RECEIVE_METRICS_SUCCESS](state, data) {
- state.isLoading = false;
- state.hasPrometheusData = true;
- state.graphData = data;
- },
- [types.RECEIVE_METRICS_NODATA_SUCCESS](state) {
- state.isLoading = false;
- state.hasPrometheusData = false;
- },
- [types.RECEIVE_METRICS_ERROR](state, error) {
- state.hasPrometheusData = false;
- state.error = error;
- },
- [types.RECEIVE_METRICS_NO_PROMETHEUS](state) {
- state.hasPrometheusData = false;
- state.hasPrometheus = false;
- },
-};
diff --git a/app/assets/javascripts/serverless/store/state.js b/app/assets/javascripts/serverless/store/state.js
deleted file mode 100644
index 353bfcf3fed..00000000000
--- a/app/assets/javascripts/serverless/store/state.js
+++ /dev/null
@@ -1,22 +0,0 @@
-export default (
- initialState = { clustersPath: null, helpPath: null, emptyImagePath: null, statusPath: null },
-) => ({
- clustersPath: initialState.clustersPath,
- error: null,
- helpPath: initialState.helpPath,
- installed: 'checking',
- isLoading: true,
-
- // functions
- functions: [],
- hasFunctionData: true,
- statusPath: initialState.statusPath,
-
- // function_details
- hasPrometheus: true,
- hasPrometheusData: false,
- graphData: {},
-
- // empty_state
- emptyImagePath: initialState.emptyImagePath,
-});
diff --git a/app/assets/javascripts/serverless/utils.js b/app/assets/javascripts/serverless/utils.js
deleted file mode 100644
index e218a9aa3fd..00000000000
--- a/app/assets/javascripts/serverless/utils.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// Validate that the object coming in has valid query details and results
-export const validateGraphData = (data) =>
- data.queries &&
- Array.isArray(data.queries) &&
- data.queries.filter((query) => {
- if (Array.isArray(query.result)) {
- return query.result.filter((res) => Array.isArray(res.values)).length === query.result.length;
- }
-
- return false;
- }).length === data.queries.length;
-
-export const translate = (functions) =>
- functions.reduce(
- (acc, func) =>
- Object.assign(acc, {
- [func.environment_scope]: (acc[func.environment_scope] || []).concat([func]),
- }),
- {},
- );
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
index 578c344da02..ef40de82d01 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -101,14 +101,15 @@ export default {
<template>
<!-- must be `d-inline-block` or parent flex-basis causes width issues -->
<gl-link
- v-gl-tooltip="tooltipOption"
:href="assigneeUrl"
:title="tooltipTitle"
- class="gl-display-inline-block"
+ :data-user-id="user.id"
+ data-placement="left"
+ class="gl-display-inline-block js-user-link"
>
<!-- use d-flex so that slot can be appropriately styled -->
<span class="gl-display-flex">
- <assignee-avatar :user="user" :img-size="32" :issuable-type="issuableType" />
+ <assignee-avatar :user="user" :img-size="24" :issuable-type="issuableType" />
<slot></slot>
</span>
</gl-link>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index f98aa0dc77d..6e18cf36690 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -1,6 +1,7 @@
<script>
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { n__, __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'AssigneeTitle',
@@ -8,6 +9,7 @@ export default {
GlLoadingIcon,
GlIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
loading: {
type: Boolean,
@@ -45,7 +47,7 @@ export default {
};
</script>
<template>
- <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
+ <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold">
{{ assigneeTitle }}
<gl-loading-icon v-if="loading" size="sm" inline class="align-bottom" />
<a
@@ -63,6 +65,7 @@ export default {
v-if="showToggle"
:aria-label="__('Toggle sidebar')"
class="gutter-toggle float-right js-sidebar-toggle"
+ :class="{ 'gl-display-block gl-md-display-none!': glFeatures.movedMrSidebar }"
href="#"
role="button"
>
diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue
index 856687c00ae..50b1955abcc 100644
--- a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue
@@ -124,7 +124,11 @@ export default {
:issuable-type="issuableType"
/>
<button v-if="hasMoreThanTwoAssignees" class="btn-link" type="button">
- <span class="avatar-counter sidebar-avatar-counter"> {{ sidebarAvatarCounter }} </span>
+ <span
+ class="avatar-counter sidebar-avatar-counter gl-display-flex gl-align-items-center gl-pl-3"
+ >
+ {{ sidebarAvatarCounter }}
+ </span>
<gl-icon
v-if="isMergeRequest && !allAssigneesCanMerge"
name="warning-solid"
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
index 19f588b28be..e9c68008143 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue
@@ -3,6 +3,11 @@ import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui';
import { IssuableType } from '~/issues/constants';
import { s__, sprintf } from '~/locale';
+const AVAILABILITY_STATUS = {
+ NOT_SET: 'NOT_SET',
+ BUSY: 'BUSY',
+};
+
export default {
components: {
GlAvatarLabeled,
@@ -22,12 +27,17 @@ export default {
},
computed: {
userLabel() {
- if (!this.user.status) {
- return this.user.name;
+ const { name, status } = this.user;
+ if (!status || status?.availability !== AVAILABILITY_STATUS.BUSY) {
+ return name;
}
- return sprintf(s__('UserAvailability|%{author} (Busy)'), {
- author: this.user.name,
- });
+ return sprintf(
+ s__('UserAvailability|%{author} (Busy)'),
+ {
+ author: name,
+ },
+ false,
+ );
},
hasCannotMergeIcon() {
return this.issuableType === IssuableType.MergeRequest && !this.user.canMerge;
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index 8717d205dcb..01d29da5486 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -35,13 +35,6 @@ export default {
firstUser() {
return this.users[0];
},
- hasOneUser() {
- if (this.showVerticalList) {
- return false;
- }
-
- return this.users.length === 1;
- },
hiddenAssigneesLabel() {
const { numberOfHiddenAssignees } = this;
return sprintf(__('+ %{numberOfHiddenAssignees} more'), { numberOfHiddenAssignees });
@@ -90,30 +83,15 @@ export default {
</script>
<template>
- <assignee-avatar-link
- v-if="hasOneUser"
- tooltip-placement="left"
- :tooltip-has-name="false"
- :user="firstUser"
- :issuable-type="issuableType"
- >
- <div class="ml-2 gl-line-height-normal">
- <user-name-with-status :name="firstUser.name" :availability="userAvailability(firstUser)" />
- <div>{{ username }}</div>
- </div>
- </assignee-avatar-link>
- <div v-else>
+ <div>
<div class="gl-display-flex gl-flex-wrap">
<div
v-for="(user, index) in uncollapsedUsers"
:key="user.id"
:class="{
- 'user-item': !showVerticalList,
- 'gl-display-inline-block': !showVerticalList,
- 'gl-display-grid gl-align-items-center': showVerticalList,
- 'gl-mb-3': index !== users.length - 1 && showVerticalList,
+ 'gl-mb-3': index !== users.length - 1,
}"
- class="assignee-grid"
+ class="assignee-grid gl-display-grid gl-align-items-center gl-w-full"
>
<assignee-avatar-link
:user="user"
@@ -123,12 +101,10 @@ export default {
data-css-area="user"
>
<div
- v-if="showVerticalList"
- class="gl-ml-3 gl-line-height-normal gl-display-grid"
+ class="gl-ml-3 gl-line-height-normal gl-display-grid gl-align-items-center"
data-testid="username"
>
<user-name-with-status :name="user.name" :availability="userAvailability(user)" />
- <span>@{{ user.username }}</span>
</div>
</assignee-avatar-link>
<attention-requested-toggle
diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue
index cdc1c65a516..031de669489 100644
--- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue
+++ b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue
@@ -5,9 +5,8 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
export default {
i18n: {
- attentionRequestedReviewer: __('Request attention to review'),
- attentionRequestedAssignee: __('Request attention'),
- removeAttentionRequested: __('Remove attention request'),
+ addAttentionRequest: __('Add attention request'),
+ removeAttentionRequest: __('Remove attention request'),
attentionRequestedNoPermission: __('Attention requested'),
noAttentionRequestedNoPermission: __('No attention request'),
},
@@ -36,20 +35,35 @@ export default {
tooltipTitle() {
if (this.user.attention_requested) {
if (this.user.can_update_merge_request) {
- return this.$options.i18n.removeAttentionRequested;
+ return this.$options.i18n.removeAttentionRequest;
}
return this.$options.i18n.attentionRequestedNoPermission;
}
if (this.user.can_update_merge_request) {
- return this.type === 'reviewer'
- ? this.$options.i18n.attentionRequestedReviewer
- : this.$options.i18n.attentionRequestedAssignee;
+ return this.$options.i18n.addAttentionRequest;
}
return this.$options.i18n.noAttentionRequestedNoPermission;
},
+ request() {
+ const state = {
+ variant: 'default',
+ icon: 'attention',
+ direction: 'add',
+ };
+
+ if (this.user.attention_requested) {
+ Object.assign(state, {
+ variant: 'warning',
+ icon: 'attention-solid',
+ direction: 'remove',
+ });
+ }
+
+ return state;
+ },
},
methods: {
toggleAttentionRequired() {
@@ -60,6 +74,7 @@ export default {
this.$emit('toggle-attention-requested', {
user: this.user,
callback: this.toggleAttentionRequiredComplete,
+ direction: this.request.direction,
});
},
toggleAttentionRequiredComplete() {
@@ -77,8 +92,8 @@ export default {
>
<gl-button
:loading="loading"
- :variant="user.attention_requested ? 'warning' : 'default'"
- :icon="user.attention_requested ? 'attention-solid' : 'attention'"
+ :variant="request.variant"
+ :icon="request.icon"
:aria-label="tooltipTitle"
:class="{ 'gl-pointer-events-none': !user.can_update_merge_request }"
size="small"
diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
index 37a44eb8f01..6afaee91d7a 100644
--- a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_content.vue
@@ -1,10 +1,13 @@
<script>
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
+import { GlIcon, GlAlert, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { IssuableType, WorkspaceType } from '~/issues/constants';
+import { confidentialityInfoText } from '~/vue_shared/constants';
export default {
components: {
GlIcon,
+ GlAlert,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -20,12 +23,11 @@ export default {
},
},
computed: {
- confidentialText() {
- return this.confidential
- ? sprintf(__('This %{issuableType} is confidential'), {
- issuableType: this.issuableType,
- })
- : __('Not confidential');
+ confidentialBodyText() {
+ return confidentialityInfoText(
+ this.issuableType === IssuableType.Epic ? WorkspaceType.group : WorkspaceType.project,
+ this.issuableType,
+ );
},
confidentialIcon() {
return this.confidential ? 'eye-slash' : 'eye';
@@ -59,6 +61,17 @@ export default {
class="sidebar-item-icon inline hide-collapsed"
:class="{ 'is-active': confidential }"
/>
- <span class="hide-collapsed" data-testid="confidential-text">{{ confidentialText }}</span>
+ <span class="hide-collapsed" data-testid="confidential-text">
+ {{ tooltipLabel }}
+ <gl-alert
+ v-if="confidential"
+ :show-icon="false"
+ :dismissible="false"
+ variant="warning"
+ class="gl-mt-3"
+ >
+ {{ confidentialBodyText }}
+ </gl-alert>
+ </span>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue
index 209d1cca360..71e40fde77d 100644
--- a/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue
@@ -8,7 +8,7 @@ import { confidentialityQueries } from '~/sidebar/constants';
export default {
i18n: {
confidentialityOnWarning: __(
- 'You are going to turn on confidentiality. Only team members with %{strongStart}at least Reporter access%{strongEnd} will be able to see and leave comments on the %{issuableType}.',
+ 'You are going to turn on confidentiality. Only %{context} members with %{strongStart}at least Reporter role%{strongEnd} can view or be notified about this %{issuableType}.',
),
confidentialityOffWarning: __(
'You are going to turn off the confidentiality. This means %{strongStart}everyone%{strongEnd} will be able to see and leave a comment on this %{issuableType}.',
@@ -53,6 +53,9 @@ export default {
? this.$options.i18n.confidentialityOffWarning
: this.$options.i18n.confidentialityOnWarning;
},
+ context() {
+ return this.issuableType === IssuableType.Issue ? __('project') : __('group');
+ },
workspacePath() {
return this.issuableType === IssuableType.Issue
? {
@@ -119,6 +122,7 @@ export default {
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
+ <template #context>{{ context }}</template>
<template #issuableType>{{ issuableType }}</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue b/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
index 950647f1cb2..67f36f65b5d 100644
--- a/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
+++ b/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
@@ -2,7 +2,7 @@
import { GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import createFlash from '~/flash';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ISSUE } from '~/graphql_shared/constants';
import getIssueCrmContactsQuery from './queries/get_issue_crm_contacts.query.graphql';
import issueCrmContactsSubscription from './queries/issue_crm_contacts.subscription.graphql';
@@ -21,6 +21,10 @@ export default {
type: String,
required: true,
},
+ groupIssuesPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -85,6 +89,10 @@ export default {
Boolean,
);
},
+ getIssuesPath(contactId) {
+ const id = getIdFromGraphQLId(contactId);
+ return `${this.groupIssuesPath}?crm_contact_id=${id}`;
+ },
},
};
</script>
@@ -100,7 +108,7 @@ export default {
><gl-icon name="question-o"
/></gl-link>
</div>
- <div class="title hide-collapsed gl-mb-2 gl-line-height-20">
+ <div class="title hide-collapsed gl-mb-2 gl-line-height-20 gl-font-weight-bold">
{{ contactsLabel }}
</div>
<div class="hide-collapsed gl-display-flex gl-flex-wrap">
@@ -110,8 +118,8 @@ export default {
:key="index"
class="gl-pr-2"
>
- <span :id="`contact_${index}`" class="gl-font-weight-bold"
- >{{ contact.firstName }} {{ contact.lastName }}{{ divider(index) }}</span
+ <gl-link :id="`contact_${index}`" :href="getIssuesPath(contact.id)"
+ >{{ contact.firstName }} {{ contact.lastName }}{{ divider(index) }}</gl-link
>
<gl-popover
v-if="shouldShowPopover(contact)"
diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue b/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue
index 87cf1c29fb0..627b8452508 100644
--- a/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue
+++ b/app/assets/javascripts/sidebar/components/date/sidebar_formatted_date.vue
@@ -35,7 +35,7 @@ export default {
<template>
<div class="gl-display-flex gl-align-items-center hide-collapsed">
<span
- :class="hasDate ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'"
+ :class="hasDate ? 'gl-text-gray-900' : 'gl-text-gray-500'"
data-testid="sidebar-date-value"
>
{{ formattedDate }}
diff --git a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
index cb49f329f7e..699d1bebea1 100644
--- a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
@@ -1,7 +1,9 @@
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import { mapGetters } from 'vuex';
-import { __ } from '~/locale';
+import { mapGetters, mapActions } from 'vuex';
+import { __, sprintf } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import createFlash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
@@ -23,11 +25,11 @@ export default {
editForm,
GlIcon,
},
-
directives: {
GlTooltip: GlTooltipDirective,
},
-
+ mixins: [glFeatureFlagMixin()],
+ inject: ['fullPath'],
props: {
isEditable: {
required: true,
@@ -41,6 +43,9 @@ export default {
},
computed: {
...mapGetters(['getNoteableData']),
+ isMergeRequest() {
+ return this.getNoteableData.targetType === 'merge_request' && this.glFeatures.movedMrSidebar;
+ },
issuableDisplayName() {
const isInIssuePage = this.getNoteableData.targetType === this.$options.issue;
return isInIssuePage ? __('issue') : __('merge request');
@@ -66,17 +71,49 @@ export default {
},
methods: {
+ ...mapActions(['updateLockedAttribute']),
toggleForm() {
if (this.isEditable) {
this.isLockDialogOpen = !this.isLockDialogOpen;
}
},
+ toggleLocked() {
+ this.isLoading = true;
+
+ this.updateLockedAttribute({
+ locked: !this.isLocked,
+ fullPath: this.fullPath,
+ })
+ .catch(() => {
+ const flashMessage = __(
+ 'Something went wrong trying to change the locked state of this %{issuableDisplayName}',
+ );
+ createFlash({
+ message: sprintf(flashMessage, { issuableDisplayName: this.issuableDisplayName }),
+ });
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
+ },
},
};
</script>
<template>
- <div class="block issuable-sidebar-item lock">
+ <li v-if="isMergeRequest" class="gl-new-dropdown-item">
+ <button type="button" class="dropdown-item" @click="toggleLocked">
+ <span class="gl-new-dropdown-item-text-wrapper">
+ <template v-if="isLocked">
+ {{ __('Unlock merge request') }}
+ </template>
+ <template v-else>
+ {{ __('Lock merge request') }}
+ </template>
+ </span>
+ </button>
+ </li>
+ <div v-else class="block issuable-sidebar-item lock">
<div
v-gl-tooltip.left.viewport="{ title: tooltipLabel }"
class="sidebar-collapsed-icon"
@@ -86,7 +123,7 @@ export default {
<gl-icon :name="lockStatus.icon" class="sidebar-item-icon is-active" />
</div>
- <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
+ <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold">
{{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }}
<a
v-if="isEditable"
@@ -111,12 +148,6 @@ export default {
/>
<div data-testid="lock-status" class="sidebar-item-value" :class="lockStatus.class">
- <gl-icon
- :size="16"
- :name="lockStatus.icon"
- class="sidebar-item-icon"
- :class="lockStatus.iconClass"
- />
{{ lockStatus.displayText }}
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 3fd35de2132..77e41648e9b 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -101,7 +101,10 @@ export default {
<gl-loading-icon v-if="loading" size="sm" />
<span v-else data-testid="collapsed-count"> {{ participantCount }} </span>
</div>
- <div v-if="showParticipantLabel" class="title hide-collapsed gl-mb-2 gl-line-height-20">
+ <div
+ v-if="showParticipantLabel"
+ class="title hide-collapsed gl-mb-2! gl-line-height-20 gl-font-weight-bold"
+ >
<gl-loading-icon v-if="loading" size="sm" :inline="true" />
{{ participantLabel }}
</div>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
index 60d8fb4d408..e09b5d913f7 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/collapsed_reviewer_list.vue
@@ -96,7 +96,11 @@ export default {
<gl-icon v-if="hasNoUsers" name="user" :aria-label="__('None')" />
<collapsed-reviewer v-for="user in collapsedUsers" :key="user.id" :user="user" />
<button v-if="hasMoreThanTwoReviewers" class="btn-link" type="button">
- <span class="avatar-counter sidebar-avatar-counter"> {{ sidebarAvatarCounter }} </span>
+ <span
+ class="avatar-counter sidebar-avatar-counter gl-display-flex gl-align-items-center gl-pl-3"
+ >
+ {{ sidebarAvatarCounter }}
+ </span>
<gl-icon
v-if="!allReviewersCanMerge"
name="warning-solid"
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
index a11468c8761..36a08482e69 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_avatar_link.vue
@@ -47,8 +47,6 @@ export default {
return sprintf(__('%{userName} (cannot merge)'), { userName: this.user.name });
} else if (this.cannotMerge) {
return __('Cannot merge');
- } else if (this.tooltipHasName) {
- return this.user.name;
}
return '';
@@ -70,14 +68,15 @@ export default {
<template>
<!-- must be `d-inline-block` or parent flex-basis causes width issues -->
<gl-link
- v-gl-tooltip="tooltipOption"
:href="reviewerUrl"
:title="tooltipTitle"
- class="gl-display-inline-block"
+ :data-user-id="user.id"
+ data-placement="left"
+ class="gl-display-inline-block js-user-link"
>
<!-- use d-flex so that slot can be appropriately styled -->
<span class="gl-display-flex">
- <reviewer-avatar :user="user" :img-size="32" :issuable-type="issuableType" />
+ <reviewer-avatar :user="user" :img-size="24" :issuable-type="issuableType" />
<slot :user="user"></slot>
</span>
</gl-link>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
index 367dcdb961b..933b9b11b40 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewer_title.vue
@@ -33,7 +33,7 @@ export default {
};
</script>
<template>
- <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
+ <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold">
{{ reviewerTitle }}
<gl-loading-icon v-if="loading" size="sm" inline class="align-bottom" />
<a
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index 3e6be3487b1..2f58e11c00f 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -98,7 +98,7 @@ export default {
'gl-mb-3': index !== users.length - 1,
'attention-requests': glFeatures.mrAttentionRequests,
}"
- class="gl-display-grid gl-align-items-center reviewer-grid"
+ class="gl-display-grid gl-align-items-center reviewer-grid gl-mr-2"
data-testid="reviewer"
>
<reviewer-avatar-link
@@ -108,9 +108,8 @@ export default {
class="gl-word-break-word gl-mr-2"
data-css-area="user"
>
- <div class="gl-ml-3 gl-line-height-normal gl-display-grid">
- <span>{{ user.name }}</span>
- <span>@{{ user.username }}</span>
+ <div class="gl-ml-3 gl-line-height-normal gl-display-grid gl-align-items-center">
+ {{ user.name }}
</div>
</reviewer-avatar-link>
<attention-requested-toggle
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index ec23e817127..897cab45fe4 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -331,20 +331,19 @@ export default {
:data-testid="`select-${formatIssuableAttribute.kebab}`"
:class="isClassicSidebar ? 'hide-collapsed' : 'gl-mt-3'"
>
- <span v-if="updating" class="gl-font-weight-bold">{{ selectedTitle }}</span>
+ <span v-if="updating">{{ selectedTitle }}</span>
<span v-else-if="!currentAttribute" class="gl-text-gray-500">
{{ $options.i18n.none }}
</span>
<slot
v-else
name="value"
- :attributeTitle="attributeTitle"
- :attributeUrl="attributeUrl"
- :currentAttribute="currentAttribute"
+ :attribute-title="attributeTitle"
+ :attribute-url="attributeUrl"
+ :current-attribute="currentAttribute"
>
<gl-link
v-gl-tooltip="tooltipText"
- class="gl-text-gray-900! gl-font-weight-bold"
:href="attributeUrl"
:data-qa-selector="`${formatIssuableAttribute.snake}_link`"
>
@@ -389,9 +388,9 @@ export default {
<slot
v-else
name="list"
- :attributesList="attributesList"
- :isAttributeChecked="isAttributeChecked"
- :updateAttribute="updateAttribute"
+ :attributes-list="attributesList"
+ :is-attribute-checked="isAttributeChecked"
+ :update-attribute="updateAttribute"
>
<gl-dropdown-item
v-for="attrItem in attributesList"
diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
index 056b3e98a1c..7551b181a58 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
@@ -110,7 +110,7 @@ export default {
<template>
<div>
<div
- class="gl-display-flex gl-align-items-center gl-line-height-20 gl-mb-2 gl-text-gray-900"
+ class="gl-display-flex gl-align-items-center gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold"
@click.self="collapse"
>
<span class="hide-collapsed" data-testid="title" @click="collapse">
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
index 7a10a9f3a4c..1bafa845665 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
@@ -5,6 +5,7 @@ import { IssuableType } from '~/issues/constants';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { subscribedQueries, Tracking } from '~/sidebar/constants';
const ICON_ON = 'notifications';
@@ -25,6 +26,7 @@ export default {
GlToggle,
SidebarEditableItem,
},
+ mixins: [glFeatureFlagMixin()],
props: {
iid: {
type: String,
@@ -82,6 +84,9 @@ export default {
},
},
computed: {
+ isMergeRequest() {
+ return this.issuableType === IssuableType.MergeRequest && this.glFeatures.movedMrSidebar;
+ },
isLoading() {
return this.$apollo.queries?.subscribed?.loading || this.loading;
},
@@ -171,7 +176,20 @@ export default {
</script>
<template>
+ <li v-if="isMergeRequest" class="gl-new-dropdown-item">
+ <button type="button" class="dropdown-item" @click="toggleSubscribed">
+ <span class="gl-new-dropdown-item-text-wrapper">
+ <template v-if="subscribed">
+ {{ __('Turn off notifications') }}
+ </template>
+ <template v-else>
+ {{ __('Turn on notifications') }}
+ </template>
+ </span>
+ </button>
+ </li>
<sidebar-editable-item
+ v-else
ref="editable"
:title="$options.i18n.notifications"
:tracking="$options.tracking"
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
index 5d4031ac68b..d9797961d40 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
@@ -78,9 +78,9 @@ export default {
},
},
fields: [
- { key: 'spentAt', label: __('Spent At'), sortable: true },
+ { key: 'spentAt', label: __('Spent At'), sortable: true, tdClass: 'gl-w-quarter' },
{ key: 'user', label: __('User'), sortable: true },
- { key: 'timeSpent', label: __('Time Spent'), sortable: true },
+ { key: 'timeSpent', label: __('Time Spent'), sortable: true, tdClass: 'gl-w-15' },
{ key: 'summary', label: __('Summary / Note'), sortable: true },
],
};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index fdbcef22bba..057bb9f0100 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -204,7 +204,7 @@ export default {
:time-estimate-human-readable="humanTimeEstimate"
/>
<div
- class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center"
+ class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center gl-font-weight-bold gl-mr-3"
>
{{ __('Time tracking') }}
<gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" class="gl-ml-2" inline />
@@ -221,8 +221,7 @@ export default {
</div>
<div v-if="!isTimeTrackingInfoLoading" class="hide-collapsed">
<div v-if="showEstimateOnlyState" data-testid="estimateOnlyPane">
- <span class="gl-font-weight-bold">{{ $options.i18n.estimatedOnlyText }} </span
- >{{ humanTimeEstimate }}
+ <span>{{ $options.i18n.estimatedOnlyText }} </span>{{ humanTimeEstimate }}
</div>
<time-tracking-spent-only-pane
v-if="showSpentOnlyState"
@@ -250,6 +249,7 @@ export default {
</gl-link>
<gl-modal
modal-id="time-tracking-report"
+ size="lg"
:title="__('Time tracking report')"
:hide-footer="true"
>
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
index eabba619af5..482b9343e70 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
@@ -6,6 +6,9 @@ import { __, sprintf } from '~/locale';
import { todoQueries, TodoMutationTypes, todoMutations } from '~/sidebar/constants';
import { todoLabel } from '~/vue_shared/components/sidebar/todo_toggle//utils';
import TodoButton from '~/vue_shared/components/sidebar/todo_toggle/todo_button.vue';
+import Tracking from '~/tracking';
+
+const trackingMixin = Tracking.mixin();
export default {
components: {
@@ -16,6 +19,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [trackingMixin],
inject: {
isClassicSidebar: {
default: false,
@@ -151,6 +155,10 @@ export default {
message: errors[0],
});
}
+ this.track('click_todo', {
+ label: 'right_sidebar',
+ property: this.hasTodo,
+ });
},
)
.catch(() => {
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 2a7d967cb61..351bb50d941 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -218,7 +218,7 @@ function mountCrmContactsComponent() {
if (!el) return;
- const { issueId } = el.dataset;
+ const { issueId, groupIssuesPath } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
@@ -231,6 +231,7 @@ function mountCrmContactsComponent() {
createElement('crm-contacts', {
props: {
issueId,
+ groupIssuesPath,
},
}),
});
@@ -430,10 +431,7 @@ function mountLockComponent(store) {
return;
}
- const { fullPath } = getSidebarOptions();
-
- const dataNode = document.getElementById('js-lock-issue-data');
- const initialData = JSON.parse(dataNode.innerHTML);
+ const { fullPath, editable } = getSidebarOptions();
// eslint-disable-next-line no-new
new Vue({
@@ -446,7 +444,7 @@ function mountLockComponent(store) {
render: (createElement) =>
createElement(IssuableLockForm, {
props: {
- isEditable: initialData.is_editable,
+ isEditable: editable,
},
}),
});
diff --git a/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql b/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
new file mode 100644
index 00000000000..d9b9c04fd63
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
@@ -0,0 +1,7 @@
+mutation mergeRequestRemoveAttentionRequest($projectPath: ID!, $iid: String!, $userId: UserID!) {
+ mergeRequestRemoveAttentionRequest(
+ input: { projectPath: $projectPath, iid: $iid, userId: $userId }
+ ) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql b/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
new file mode 100644
index 00000000000..99a86e4fe5c
--- /dev/null
+++ b/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
@@ -0,0 +1,5 @@
+mutation mergeRequestRequestAttention($projectPath: ID!, $iid: String!, $userId: UserID!) {
+ mergeRequestRequestAttention(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/sidebar/queries/reviewer_rereview.mutation.graphql b/app/assets/javascripts/sidebar/queries/reviewer_rereview.mutation.graphql
index 73765e7d77b..0d66ee0d6e5 100644
--- a/app/assets/javascripts/sidebar/queries/reviewer_rereview.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/reviewer_rereview.mutation.graphql
@@ -1,4 +1,4 @@
-mutation mergeRequestRequestRereview($projectPath: ID!, $iid: String!, $userId: ID!) {
+mutation mergeRequestRequestRereview($projectPath: ID!, $iid: String!, $userId: UserID!) {
mergeRequestReviewerRereview(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
errors
}
diff --git a/app/assets/javascripts/sidebar/queries/toggle_attention_requested.mutation.graphql b/app/assets/javascripts/sidebar/queries/toggle_attention_requested.mutation.graphql
deleted file mode 100644
index a9f4af6e1b9..00000000000
--- a/app/assets/javascripts/sidebar/queries/toggle_attention_requested.mutation.graphql
+++ /dev/null
@@ -1,7 +0,0 @@
-mutation mergeRequestToggleAttentionRequested($projectPath: ID!, $iid: String!, $userId: ID!) {
- mergeRequestToggleAttentionRequested(
- input: { projectPath: $projectPath, iid: $iid, userId: $userId }
- ) {
- errors
- }
-}
diff --git a/app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql b/app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql
index 368f06fac7f..938953ccfb2 100644
--- a/app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql
@@ -1,4 +1,4 @@
-mutation mergeRequestSetMilestone($fullPath: ID!, $iid: String!, $attributeId: ID) {
+mutation mergeRequestSetMilestone($fullPath: ID!, $iid: String!, $attributeId: MilestoneID) {
issuableSetAttribute: mergeRequestSetMilestone(
input: { projectPath: $fullPath, iid: $iid, milestoneId: $attributeId }
) {
diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js
index 90d8f2098bb..ea170203576 100644
--- a/app/assets/javascripts/sidebar/services/sidebar_service.js
+++ b/app/assets/javascripts/sidebar/services/sidebar_service.js
@@ -5,7 +5,8 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
import sidebarDetailsMRQuery from '../queries/sidebar_details_mr.query.graphql';
-import toggleAttentionRequestedMutation from '../queries/toggle_attention_requested.mutation.graphql';
+import requestAttentionMutation from '../queries/request_attention.mutation.graphql';
+import removeAttentionRequestMutation from '../queries/remove_attention_request.mutation.graphql';
const queries = {
merge_request: sidebarDetailsMRQuery,
@@ -92,9 +93,19 @@ export default class SidebarService {
});
}
- toggleAttentionRequested(userId) {
+ requestAttention(userId) {
return gqClient.mutate({
- mutation: toggleAttentionRequestedMutation,
+ mutation: requestAttentionMutation,
+ variables: {
+ userId: convertToGraphQLId(TYPE_USER, `${userId}`),
+ projectPath: this.fullPath,
+ iid: this.iid.toString(),
+ },
+ });
+ }
+ removeAttentionRequest(userId) {
+ return gqClient.mutate({
+ mutation: removeAttentionRequestMutation,
variables: {
userId: convertToGraphQLId(TYPE_USER, `${userId}`),
projectPath: this.fullPath,
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 83fb8f31dfb..7df901577b8 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -40,6 +40,7 @@ export default class SidebarMediator {
const data = { assignee_ids: assignees };
try {
+ const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('assignees', res.data.assignees);
@@ -48,6 +49,10 @@ export default class SidebarMediator {
this.store.overwrite('reviewers', res.data.reviewers);
}
+ if (currentUserHasAttention && this.store.isAddingAssignee) {
+ toast(__('Assigned user(s). Your attention request was removed.'));
+ }
+
return Promise.resolve(res);
} catch (e) {
return Promise.reject(e);
@@ -63,11 +68,16 @@ export default class SidebarMediator {
const data = { reviewer_ids: reviewers };
try {
+ const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('reviewers', res.data.reviewers);
this.store.overwrite('assignees', res.data.assignees);
+ if (currentUserHasAttention && this.store.isAddingAssignee) {
+ toast(__('Requested review. Your attention request was removed.'));
+ }
+
return Promise.resolve(res);
} catch (e) {
return Promise.reject();
@@ -98,14 +108,19 @@ export default class SidebarMediator {
}
}
- async toggleAttentionRequested(type, { user, callback }) {
+ async toggleAttentionRequested(type, { user, callback, direction }) {
+ const mutations = {
+ add: (id) => this.service.requestAttention(id),
+ remove: (id) => this.service.removeAttentionRequest(id),
+ };
+
try {
const isReviewer = type === 'reviewer';
const reviewerOrAssignee = isReviewer
? this.store.findReviewer(user)
: this.store.findAssignee(user);
- await this.service.toggleAttentionRequested(user.id);
+ await mutations[direction]?.(user.id);
if (reviewerOrAssignee.attention_requested) {
toast(
@@ -115,12 +130,22 @@ export default class SidebarMediator {
);
} else {
const currentUserId = gon.current_user_id;
+ const { currentUserHasAttention } = this.store;
if (currentUserId !== user.id) {
this.removeCurrentUserAttentionRequested();
}
- toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));
+ toast(
+ currentUserHasAttention && currentUserId !== user.id
+ ? sprintf(
+ __(
+ 'Requested attention from @%{username}. Your own attention request was removed.',
+ ),
+ { username: user.username },
+ )
+ : sprintf(__('Requested attention from @%{username}'), { username: user.username }),
+ );
}
this.store.updateReviewer(user.id, 'attention_requested');
@@ -138,7 +163,7 @@ export default class SidebarMediator {
captureError: true,
actionConfig: {
title: __('Try again'),
- clickHandler: () => this.toggleAttentionRequired(type, { user, callback }),
+ clickHandler: () => this.toggleAttentionRequired(type, { user, callback, direction }),
},
});
}
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 2caa6f4f0a0..ca85ee7fd94 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -18,7 +18,9 @@ export default class SidebarStore {
this.humanTimeSpent = '';
this.timeTrackingLimitToHours = timeTrackingLimitToHours;
this.assignees = [];
+ this.addingAssignees = [];
this.reviewers = [];
+ this.addingReviewers = [];
this.isFetching = {
assignees: true,
reviewers: true,
@@ -32,6 +34,7 @@ export default class SidebarStore {
this.subscribeDisabledDescription = '';
this.subscribed = null;
this.changing = false;
+ this.issuableType = options.issuableType;
SidebarStore.singleton = this;
}
@@ -73,12 +76,20 @@ export default class SidebarStore {
if (!this.findAssignee(assignee)) {
this.changing = true;
this.assignees.push(assignee);
+
+ if (assignee.id !== this.currentUser.id) {
+ this.addingAssignees.push(assignee.id);
+ }
}
}
addReviewer(reviewer) {
if (!this.findReviewer(reviewer)) {
this.reviewers.push(reviewer);
+
+ if (reviewer.id !== this.currentUser.id) {
+ this.addingReviewers.push(reviewer.id);
+ }
}
}
@@ -114,12 +125,14 @@ export default class SidebarStore {
if (assignee) {
this.changing = true;
this.assignees = this.assignees.filter(({ id }) => id !== assignee.id);
+ this.addingAssignees = this.addingAssignees.filter(({ id }) => id !== assignee.id);
}
}
removeReviewer(reviewer) {
if (reviewer) {
this.reviewers = this.reviewers.filter(({ id }) => id !== reviewer.id);
+ this.addingReviewers = this.addingReviewers.filter(({ id }) => id !== reviewer.id);
}
}
@@ -147,4 +160,26 @@ export default class SidebarStore {
setMoveToProjectId(moveToProjectId) {
this.moveToProjectId = moveToProjectId;
}
+
+ get currentUserHasAttention() {
+ if (!window.gon?.features?.mrAttentionRequests || !this.isMergeRequest) return false;
+
+ const currentUserId = this.currentUser.id;
+ const currentUserReviewer = this.findReviewer({ id: currentUserId });
+ const currentUserAssignee = this.findAssignee({ id: currentUserId });
+
+ return currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested;
+ }
+
+ get isAddingAssignee() {
+ return this.addingAssignees.length > 0;
+ }
+
+ get isAddingReviewer() {
+ return this.addingReviewers.length > 0;
+ }
+
+ get isMergeRequest() {
+ return this.issuableType === 'merge_request';
+ }
}
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index e4a97f08c8d..2537ec78850 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon, GlFormInput, GlFormGroup } from '@gitlab/ui';
import eventHub from '~/blob/components/eventhub';
import createFlash from '~/flash';
@@ -11,7 +11,6 @@ import {
} from '~/performance/constants';
import { performanceMarkAndMeasure } from '~/performance/utils';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
-import TitleField from '~/vue_shared/components/form/title.vue';
import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '../constants';
import { getSnippetMixin } from '../mixins/snippets';
@@ -31,10 +30,11 @@ export default {
SnippetDescriptionEdit,
SnippetVisibilityEdit,
SnippetBlobActionsEdit,
- TitleField,
FormFooterActions,
GlButton,
GlLoadingIcon,
+ GlFormInput,
+ GlFormGroup,
},
mixins: [getSnippetMixin],
inject: ['selectedLevel'],
@@ -67,6 +67,7 @@ export default {
description: '',
visibilityLevel: this.selectedLevel,
},
+ showValidation: false,
};
},
computed: {
@@ -85,8 +86,11 @@ export default {
hasValidBlobs() {
return this.actions.every((x) => x.content);
},
- updatePrevented() {
- return this.snippet.title === '' || !this.hasValidBlobs || this.isUpdating;
+ isTitleValid() {
+ return this.snippet.title !== '';
+ },
+ isFormValid() {
+ return this.isTitleValid && this.hasValidBlobs;
},
isProjectSnippet() {
return Boolean(this.projectPath);
@@ -112,6 +116,12 @@ export default {
}
return this.snippet.webUrl;
},
+ shouldShowBlobsErrors() {
+ return this.showValidation && !this.hasValidBlobs;
+ },
+ shouldShowTitleErrors() {
+ return this.showValidation && !this.isTitleValid;
+ },
},
beforeCreate() {
performanceMarkAndMeasure({ mark: SNIPPET_MARK_EDIT_APP_START });
@@ -165,6 +175,12 @@ export default {
};
},
handleFormSubmit() {
+ this.showValidation = true;
+
+ if (!this.isFormValid) {
+ return;
+ }
+
this.isUpdating = true;
this.$apollo
@@ -206,19 +222,31 @@ export default {
class="loading-animation prepend-top-20 gl-mb-6"
/>
<template v-else>
- <title-field
- id="snippet-title"
- v-model="snippet.title"
- data-qa-selector="snippet_title_field"
- required
- :autofocus="true"
- />
+ <gl-form-group
+ :label="__('Title')"
+ label-for="snippet-title"
+ :invalid-feedback="__('This field is required.')"
+ :state="!shouldShowTitleErrors"
+ >
+ <gl-form-input
+ id="snippet-title"
+ v-model="snippet.title"
+ data-testid="snippet-title-input"
+ data-qa-selector="snippet_title_field"
+ :autofocus="true"
+ />
+ </gl-form-group>
+
<snippet-description-edit
v-model="snippet.description"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
/>
- <snippet-blob-actions-edit :init-blobs="blobs" @actions="updateActions" />
+ <snippet-blob-actions-edit
+ :init-blobs="blobs"
+ :is-valid="!shouldShowBlobsErrors"
+ @actions="updateActions"
+ />
<snippet-visibility-edit
v-model="snippet.visibilityLevel"
@@ -228,12 +256,13 @@ export default {
<form-footer-actions>
<template #prepend>
<gl-button
+ class="js-no-auto-disable"
category="primary"
type="submit"
variant="confirm"
- :disabled="updatePrevented"
data-qa-selector="submit_button"
data-testid="snippet-submit-btn"
+ :disabled="isUpdating"
>{{ saveButtonLabel }}</gl-button
>
</template>
diff --git a/app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue b/app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue
index d221195ddc7..260ee496df0 100644
--- a/app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlFormGroup } from '@gitlab/ui';
import { cloneDeep } from 'lodash';
import { s__, sprintf } from '~/locale';
import { SNIPPET_MAX_BLOBS } from '../constants';
@@ -10,12 +10,18 @@ export default {
components: {
SnippetBlobEdit,
GlButton,
+ GlFormGroup,
},
props: {
initBlobs: {
type: Array,
required: true,
},
+ isValid: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
data() {
return {
@@ -124,16 +130,26 @@ export default {
</script>
<template>
<div class="form-group">
- <label :for="firstInputId">{{ s__('Snippets|Files') }}</label>
- <snippet-blob-edit
- v-for="(blobId, index) in blobIds"
- :key="blobId"
- :class="{ 'gl-mt-3': index > 0 }"
- :blob="blobs[blobId]"
- :can-delete="canDelete"
- @blob-updated="updateBlob(blobId, $event)"
- @delete="deleteBlob(blobId)"
- />
+ <gl-form-group
+ :label="s__('Snippets|Files')"
+ :label-for="firstInputId"
+ :invalid-feedback="
+ s__(
+ 'Snippets|Snippets can\'t contain empty files. Ensure all files have content, or delete them.',
+ )
+ "
+ :state="isValid"
+ >
+ <snippet-blob-edit
+ v-for="(blobId, index) in blobIds"
+ :key="blobId"
+ :class="{ 'gl-mt-3': index > 0 }"
+ :blob="blobs[blobId]"
+ :can-delete="canDelete"
+ @blob-updated="updateBlob(blobId, $event)"
+ @delete="deleteBlob(blobId)"
+ />
+ </gl-form-group>
<gl-button
:disabled="!canAdd"
data-testid="add_button"
diff --git a/app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql
index f43d53661f4..a13c143f775 100644
--- a/app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql
+++ b/app/assets/javascripts/snippets/mutations/delete_snippet.mutation.graphql
@@ -1,4 +1,4 @@
-mutation DeleteSnippet($id: ID!) {
+mutation DeleteSnippet($id: SnippetID!) {
destroySnippet(input: { id: $id }) {
errors
}
diff --git a/app/assets/javascripts/sortable/constants.js b/app/assets/javascripts/sortable/constants.js
index 7fddac00ab2..f5bb0a3b11f 100644
--- a/app/assets/javascripts/sortable/constants.js
+++ b/app/assets/javascripts/sortable/constants.js
@@ -1,3 +1,5 @@
+export const DRAG_CLASS = 'is-dragging';
+
/**
* Default config options for sortablejs.
* @type {object}
@@ -12,7 +14,7 @@
export const defaultSortableOptions = {
animation: 200,
forceFallback: true,
- fallbackClass: 'is-dragging',
+ fallbackClass: DRAG_CLASS,
fallbackOnBody: true,
ghostClass: 'is-ghost',
fallbackTolerance: 1,
diff --git a/app/assets/javascripts/sortable/utils.js b/app/assets/javascripts/sortable/utils.js
index c2c8fb03b58..88ac1295a39 100644
--- a/app/assets/javascripts/sortable/utils.js
+++ b/app/assets/javascripts/sortable/utils.js
@@ -1,13 +1,17 @@
/* global DocumentTouch */
-import { defaultSortableOptions } from './constants';
+import { defaultSortableOptions, DRAG_CLASS } from './constants';
export function sortableStart() {
- document.body.classList.add('is-dragging');
+ document.body.classList.add(DRAG_CLASS);
}
export function sortableEnd() {
- document.body.classList.remove('is-dragging');
+ document.body.classList.remove(DRAG_CLASS);
+}
+
+export function isDragging() {
+ return document.body.classList.contains(DRAG_CLASS);
}
export function getSortableDefaultOptions(options) {
diff --git a/app/assets/javascripts/static_site_editor/components/edit_area.vue b/app/assets/javascripts/static_site_editor/components/edit_area.vue
index ea775eff358..2f2efe290ec 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_area.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue
@@ -167,7 +167,9 @@ export default {
:content="editableContent"
:initial-edit-type="editorMode"
:image-root="imageRoot"
- :options="{ customRenderers }"
+ :options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ customRenderers,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
class="mb-9 pb-6 h-100"
@modeChange="onModeChange"
@input="onInputChange"
diff --git a/app/assets/javascripts/tracking/dispatch_snowplow_event.js b/app/assets/javascripts/tracking/dispatch_snowplow_event.js
index 7e596f5f36f..5daeaf1d85b 100644
--- a/app/assets/javascripts/tracking/dispatch_snowplow_event.js
+++ b/app/assets/javascripts/tracking/dispatch_snowplow_event.js
@@ -1,3 +1,4 @@
+import * as Sentry from '@sentry/browser';
import getStandardContext from './get_standard_context';
export function dispatchSnowplowEvent(
@@ -24,5 +25,11 @@ export function dispatchSnowplowEvent(
value = Number(value);
}
- return window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
+ try {
+ window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
+ return true;
+ } catch (error) {
+ Sentry.captureException(error);
+ return false;
+ }
}
diff --git a/app/assets/javascripts/tracking/tracker.js b/app/assets/javascripts/tracking/tracker.js
new file mode 100644
index 00000000000..9ad86e76b6e
--- /dev/null
+++ b/app/assets/javascripts/tracking/tracker.js
@@ -0,0 +1,267 @@
+import { LOAD_ACTION_ATTR_SELECTOR } from './constants';
+import { dispatchSnowplowEvent } from './dispatch_snowplow_event';
+import getStandardContext from './get_standard_context';
+import {
+ getEventHandlers,
+ createEventPayload,
+ renameKey,
+ getReferrersCache,
+ addReferrersCacheEntry,
+} from './utils';
+
+export const Tracker = {
+ nonInitializedQueue: [],
+ initialized: false,
+ definitionsLoaded: false,
+ definitionsManifest: {},
+ definitionsEventsQueue: [],
+ definitions: [],
+ ALLOWED_URL_HASHES: ['#diff', '#note'],
+ /**
+ * (Legacy) Determines if tracking is enabled at the user level.
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT.
+ *
+ * @returns {Boolean}
+ */
+ trackable() {
+ return !['1', 'yes'].includes(
+ window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack,
+ );
+ },
+
+ /**
+ * Determines if Snowplow is available/enabled.
+ *
+ * @returns {Boolean}
+ */
+ enabled() {
+ return typeof window.snowplow === 'function' && Tracker.trackable();
+ },
+
+ /**
+ * Dispatches a structured event per our taxonomy:
+ * https://docs.gitlab.com/ee/development/snowplow/index.html#structured-event-taxonomy.
+ *
+ * If the library is not initialized and events are trying to be
+ * dispatched (data-attributes, load-events), they will be added
+ * to a queue to be flushed afterwards.
+ *
+ * If there is an error when using the library, it will return ´false´
+ * and ´true´ otherwise.
+ *
+ * @param {...any} eventData defined event taxonomy
+ * @returns {Boolean}
+ */
+ event(...eventData) {
+ if (!Tracker.enabled()) {
+ return false;
+ }
+
+ if (!Tracker.initialized) {
+ Tracker.nonInitializedQueue.push(eventData);
+ return false;
+ }
+
+ return dispatchSnowplowEvent(...eventData);
+ },
+
+ /**
+ * Preloads event definitions.
+ *
+ * @returns {undefined}
+ */
+ loadDefinitions() {
+ // TODO: fetch definitions from the server and flush the queue
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/358256
+ Tracker.definitionsLoaded = true;
+
+ while (Tracker.definitionsEventsQueue.length) {
+ Tracker.dispatchFromDefinition(...Tracker.definitionsEventsQueue.shift());
+ }
+ },
+
+ /**
+ * Dispatches a structured event with data from its event definition.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {Boolean}
+ */
+ definition(basename, eventData = {}) {
+ if (!Tracker.enabled()) {
+ return false;
+ }
+
+ if (!(basename in Tracker.definitionsManifest)) {
+ throw new Error(`Missing Snowplow event definition "${basename}"`);
+ }
+
+ return Tracker.dispatchFromDefinition(basename, eventData);
+ },
+
+ /**
+ * Builds an event with data from a valid definition and sends it to
+ * Snowplow. If the definitions are not loaded, it pushes the data to a queue.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {Boolean}
+ */
+ dispatchFromDefinition(basename, eventData) {
+ if (!Tracker.definitionsLoaded) {
+ Tracker.definitionsEventsQueue.push([basename, eventData]);
+
+ return false;
+ }
+
+ const eventDefinition = Tracker.definitions.find((definition) => definition.key === basename);
+
+ return Tracker.event(
+ eventData.category ?? eventDefinition.category,
+ eventData.action ?? eventDefinition.action,
+ eventData,
+ );
+ },
+
+ /**
+ * Dispatches any event emitted before initialization.
+ *
+ * @returns {undefined}
+ */
+ flushPendingEvents() {
+ Tracker.initialized = true;
+
+ while (Tracker.nonInitializedQueue.length) {
+ dispatchSnowplowEvent(...Tracker.nonInitializedQueue.shift());
+ }
+ },
+
+ /**
+ * Attaches event handlers for data-attributes powered events.
+ *
+ * @param {String} category - the default category for all events
+ * @param {HTMLElement} parent - element containing data-attributes
+ * @returns {Array}
+ */
+ bindDocument(category = document.body.dataset.page, parent = document) {
+ if (!Tracker.enabled() || parent.trackingBound) {
+ return [];
+ }
+
+ // eslint-disable-next-line no-param-reassign
+ parent.trackingBound = true;
+
+ const handlers = getEventHandlers(category, (...args) => Tracker.event(...args));
+ handlers.forEach((event) => parent.addEventListener(event.name, event.func));
+
+ return handlers;
+ },
+
+ /**
+ * Attaches event handlers for load-events (on render).
+ *
+ * @param {String} category - the default category for all events
+ * @param {HTMLElement} parent - element containing event targets
+ * @returns {Array}
+ */
+ trackLoadEvents(category = document.body.dataset.page, parent = document) {
+ if (!Tracker.enabled()) {
+ return [];
+ }
+
+ const loadEvents = parent.querySelectorAll(LOAD_ACTION_ATTR_SELECTOR);
+
+ loadEvents.forEach((element) => {
+ const { action, data } = createEventPayload(element);
+ Tracker.event(category, action, data);
+ });
+
+ return loadEvents;
+ },
+
+ /**
+ * Enable Snowplow automatic form tracking.
+ * The config param requires at least one array of either forms
+ * class names, or field name attributes.
+ * https://docs.gitlab.com/ee/development/snowplow/index.html#form-tracking.
+ *
+ * @param {Object} config
+ * @param {Array} contexts
+ * @returns {undefined}
+ */
+ enableFormTracking(config, contexts = []) {
+ if (!Tracker.enabled()) {
+ return;
+ }
+
+ if (!Array.isArray(config?.forms?.allow) && !Array.isArray(config?.fields?.allow)) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('Unable to enable form event tracking without allow rules.');
+ }
+
+ // Ignore default/standard schema
+ const standardContext = getStandardContext();
+ const userProvidedContexts = contexts.filter(
+ (context) => context.schema !== standardContext.schema,
+ );
+
+ const mappedConfig = {};
+ if (config.forms) {
+ mappedConfig.forms = renameKey(config.forms, 'allow', 'whitelist');
+ }
+
+ if (config.fields) {
+ mappedConfig.fields = renameKey(config.fields, 'allow', 'whitelist');
+ }
+
+ const enabler = () => window.snowplow('enableFormTracking', mappedConfig, userProvidedContexts);
+
+ if (document.readyState === 'complete') {
+ enabler();
+ } else {
+ document.addEventListener('readystatechange', () => {
+ if (document.readyState === 'complete') {
+ enabler();
+ }
+ });
+ }
+ },
+
+ /**
+ * Replaces the URL and referrer for the default web context
+ * if the replacements are available.
+ *
+ * @returns {undefined}
+ */
+ setAnonymousUrls() {
+ const { snowplowPseudonymizedPageUrl: pageUrl } = window.gl;
+
+ if (!pageUrl) {
+ return;
+ }
+
+ const referrers = getReferrersCache();
+ const pageLinks = Object.seal({
+ url: pageUrl,
+ referrer: '',
+ originalUrl: window.location.href,
+ });
+
+ const appendHash = Tracker.ALLOWED_URL_HASHES.some((prefix) =>
+ window.location.hash.startsWith(prefix),
+ );
+ const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
+ window.snowplow('setCustomUrl', customUrl);
+
+ if (document.referrer) {
+ const node = referrers.find((links) => links.originalUrl === document.referrer);
+
+ if (node) {
+ pageLinks.referrer = node.url;
+ window.snowplow('setReferrerUrl', pageLinks.referrer);
+ }
+ }
+
+ addReferrersCacheEntry(referrers, pageLinks);
+ },
+};
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index f299c57b33f..923aea433f1 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -1,268 +1,7 @@
-import { LOAD_ACTION_ATTR_SELECTOR } from './constants';
-import { dispatchSnowplowEvent } from './dispatch_snowplow_event';
-import getStandardContext from './get_standard_context';
-import {
- getEventHandlers,
- createEventPayload,
- renameKey,
- addExperimentContext,
- getReferrersCache,
- addReferrersCacheEntry,
-} from './utils';
-
-const ALLOWED_URL_HASHES = ['#diff', '#note'];
-
-export default class Tracking {
- static nonInitializedQueue = [];
- static initialized = false;
- static definitionsLoaded = false;
- static definitionsManifest = {};
- static definitionsEventsQueue = [];
- static definitions = [];
-
- /**
- * (Legacy) Determines if tracking is enabled at the user level.
- * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT.
- *
- * @returns {Boolean}
- */
- static trackable() {
- return !['1', 'yes'].includes(
- window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack,
- );
- }
-
- /**
- * Determines if Snowplow is available/enabled.
- *
- * @returns {Boolean}
- */
- static enabled() {
- return typeof window.snowplow === 'function' && this.trackable();
- }
-
- /**
- * Dispatches a structured event per our taxonomy:
- * https://docs.gitlab.com/ee/development/snowplow/index.html#structured-event-taxonomy.
- *
- * If the library is not initialized and events are trying to be
- * dispatched (data-attributes, load-events), they will be added
- * to a queue to be flushed afterwards.
- *
- * @param {...any} eventData defined event taxonomy
- * @returns {undefined|Boolean}
- */
- static event(...eventData) {
- if (!this.enabled()) {
- return false;
- }
-
- if (!this.initialized) {
- this.nonInitializedQueue.push(eventData);
- return false;
- }
-
- return dispatchSnowplowEvent(...eventData);
- }
-
- /**
- * Preloads event definitions.
- *
- * @returns {undefined}
- */
- static loadDefinitions() {
- // TODO: fetch definitions from the server and flush the queue
- // See https://gitlab.com/gitlab-org/gitlab/-/issues/358256
- this.definitionsLoaded = true;
-
- while (this.definitionsEventsQueue.length) {
- this.dispatchFromDefinition(...this.definitionsEventsQueue.shift());
- }
- }
-
- /**
- * Dispatches a structured event with data from its event definition.
- *
- * @param {String} basename
- * @param {Object} eventData
- * @returns {undefined|Boolean}
- */
- static definition(basename, eventData = {}) {
- if (!this.enabled()) {
- return false;
- }
-
- if (!(basename in this.definitionsManifest)) {
- throw new Error(`Missing Snowplow event definition "${basename}"`);
- }
-
- return this.dispatchFromDefinition(basename, eventData);
- }
-
- /**
- * Builds an event with data from a valid definition and sends it to
- * Snowplow. If the definitions are not loaded, it pushes the data to a queue.
- *
- * @param {String} basename
- * @param {Object} eventData
- * @returns {undefined|Boolean}
- */
- static dispatchFromDefinition(basename, eventData) {
- if (!this.definitionsLoaded) {
- this.definitionsEventsQueue.push([basename, eventData]);
-
- return false;
- }
-
- const eventDefinition = this.definitions.find((definition) => definition.key === basename);
-
- return this.event(
- eventData.category ?? eventDefinition.category,
- eventData.action ?? eventDefinition.action,
- eventData,
- );
- }
-
- /**
- * Dispatches any event emitted before initialization.
- *
- * @returns {undefined}
- */
- static flushPendingEvents() {
- this.initialized = true;
-
- while (this.nonInitializedQueue.length) {
- dispatchSnowplowEvent(...this.nonInitializedQueue.shift());
- }
- }
-
- /**
- * Attaches event handlers for data-attributes powered events.
- *
- * @param {String} category - the default category for all events
- * @param {HTMLElement} parent - element containing data-attributes
- * @returns {Array}
- */
- static bindDocument(category = document.body.dataset.page, parent = document) {
- if (!this.enabled() || parent.trackingBound) {
- return [];
- }
-
- // eslint-disable-next-line no-param-reassign
- parent.trackingBound = true;
-
- const handlers = getEventHandlers(category, (...args) => this.event(...args));
- handlers.forEach((event) => parent.addEventListener(event.name, event.func));
-
- return handlers;
- }
-
- /**
- * Attaches event handlers for load-events (on render).
- *
- * @param {String} category - the default category for all events
- * @param {HTMLElement} parent - element containing event targets
- * @returns {Array}
- */
- static trackLoadEvents(category = document.body.dataset.page, parent = document) {
- if (!this.enabled()) {
- return [];
- }
-
- const loadEvents = parent.querySelectorAll(LOAD_ACTION_ATTR_SELECTOR);
-
- loadEvents.forEach((element) => {
- const { action, data } = createEventPayload(element);
- this.event(category, action, data);
- });
-
- return loadEvents;
- }
-
- /**
- * Enable Snowplow automatic form tracking.
- * The config param requires at least one array of either forms
- * class names, or field name attributes.
- * https://docs.gitlab.com/ee/development/snowplow/index.html#form-tracking.
- *
- * @param {Object} config
- * @param {Array} contexts
- * @returns {undefined}
- */
- static enableFormTracking(config, contexts = []) {
- if (!this.enabled()) {
- return;
- }
-
- if (!Array.isArray(config?.forms?.allow) && !Array.isArray(config?.fields?.allow)) {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- throw new Error('Unable to enable form event tracking without allow rules.');
- }
-
- // Ignore default/standard schema
- const standardContext = getStandardContext();
- const userProvidedContexts = contexts.filter(
- (context) => context.schema !== standardContext.schema,
- );
-
- const mappedConfig = {};
- if (config.forms) {
- mappedConfig.forms = renameKey(config.forms, 'allow', 'whitelist');
- }
-
- if (config.fields) {
- mappedConfig.fields = renameKey(config.fields, 'allow', 'whitelist');
- }
-
- const enabler = () => window.snowplow('enableFormTracking', mappedConfig, userProvidedContexts);
-
- if (document.readyState === 'complete') {
- enabler();
- } else {
- document.addEventListener('readystatechange', () => {
- if (document.readyState === 'complete') {
- enabler();
- }
- });
- }
- }
-
- /**
- * Replaces the URL and referrer for the default web context
- * if the replacements are available.
- *
- * @returns {undefined}
- */
- static setAnonymousUrls() {
- const { snowplowPseudonymizedPageUrl: pageUrl } = window.gl;
-
- if (!pageUrl) {
- return;
- }
-
- const referrers = getReferrersCache();
- const pageLinks = Object.seal({
- url: pageUrl,
- referrer: '',
- originalUrl: window.location.href,
- });
-
- const appendHash = ALLOWED_URL_HASHES.some((prefix) => window.location.hash.startsWith(prefix));
- const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
- window.snowplow('setCustomUrl', customUrl);
-
- if (document.referrer) {
- const node = referrers.find((links) => links.originalUrl === document.referrer);
-
- if (node) {
- pageLinks.referrer = node.url;
- window.snowplow('setReferrerUrl', pageLinks.referrer);
- }
- }
-
- addReferrersCacheEntry(referrers, pageLinks);
- }
+import { Tracker } from 'jh_else_ce/tracking/tracker';
+import { addExperimentContext } from './utils';
+const Tracking = Object.assign(Tracker, {
/**
* Returns an implementation of this class in the form of
* a Vue mixin.
@@ -270,7 +9,7 @@ export default class Tracking {
* @param {Object} opts - default options for all events
* @returns {Object}
*/
- static mixin(opts = {}) {
+ mixin(opts = {}) {
return {
computed: {
trackingCategory() {
@@ -294,5 +33,7 @@ export default class Tracking {
},
},
};
- }
-}
+ },
+});
+
+export default Tracking;
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 4544373d8aa..438ae2bc1bc 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -32,6 +32,7 @@ const populateUserInfo = (user) => {
([userData, status]) => {
if (userData) {
Object.assign(user, {
+ id: userId,
avatarUrl: userData.avatar_url,
bot: userData.bot,
username: userData.username,
@@ -42,6 +43,7 @@ const populateUserInfo = (user) => {
websiteUrl: userData.website_url,
pronouns: userData.pronouns,
localTime: userData.local_time,
+ isFollowed: userData.is_followed,
loaded: true,
});
}
@@ -97,15 +99,29 @@ export default function addPopovers(elements = document.querySelectorAll('.js-us
bio: null,
workInformation: null,
status: null,
+ isFollowed: false,
loaded: false,
};
const renderedPopover = new UserPopoverComponent({
propsData: {
target: el,
user,
+ placement: el.dataset.placement || 'top',
},
});
+ const { userId } = el.dataset;
+
+ renderedPopover.$on('follow', () => {
+ UsersCache.updateById(userId, { is_followed: true });
+ user.isFollowed = true;
+ });
+
+ renderedPopover.$on('unfollow', () => {
+ UsersCache.updateById(userId, { is_followed: false });
+ user.isFollowed = false;
+ });
+
initializedPopovers.set(el, renderedPopover);
renderedPopover.$mount();
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index f7a5589af90..e1e5cc565c6 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -35,7 +35,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
}
- const { handleClick } = options;
+ const { handleClick, autoAssignToMe } = options;
const userSelect = this;
$els.each((i, dropdown) => {
@@ -172,10 +172,7 @@ function UsersSelect(currentUser, els, options = {}) {
});
};
- $assignToMeLink.on('click', (e) => {
- e.preventDefault();
- $(e.currentTarget).hide();
-
+ const onAssignToMeClick = () => {
if ($dropdown.data('multiSelect')) {
assignYourself();
checkMaxSelect();
@@ -194,8 +191,19 @@ function UsersSelect(currentUser, els, options = {}) {
.text(gon.current_user_fullname)
.removeClass('is-default');
}
+ };
+
+ $assignToMeLink.on('click', (e) => {
+ e.preventDefault();
+ $(e.currentTarget).hide();
+ onAssignToMeClick();
});
+ if (autoAssignToMe) {
+ $assignToMeLink.hide();
+ onAssignToMeClick();
+ }
+
$block.on('click', '.js-assign-yourself', (e) => {
e.preventDefault();
return assignTo(userSelect.currentUser.id);
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
index 492e68b636f..437d035fbf5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
@@ -12,6 +12,11 @@ export default {
},
mixins: [glFeatureFlagMixin()],
props: {
+ state: {
+ type: String,
+ required: false,
+ default: '',
+ },
isSquashEnabled: {
type: Boolean,
required: false,
@@ -30,8 +35,16 @@ export default {
type: String,
required: true,
},
+ mergeCommitSha: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
+ isMerged() {
+ return this.state === 'merged';
+ },
targetBranchEscaped() {
return escape(this.targetBranch);
},
@@ -39,6 +52,22 @@ export default {
return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount);
},
message() {
+ if (this.glFeatures.restructuredMrWidget) {
+ if (this.state === 'closed') {
+ return s__('mrWidgetCommitsAdded|The changes were not merged into %{targetBranch}.');
+ } else if (this.isMerged) {
+ return s__(
+ 'mrWidgetCommitsAdded|Changes merged into %{targetBranch} with %{mergeCommitSha}%{squashedCommits}.',
+ );
+ }
+
+ return this.isFastForwardEnabled
+ ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
+ : s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}%{squashedCommits}.',
+ );
+ }
+
return this.isFastForwardEnabled
? s__('mrWidgetCommitsAdded|Adds %{commitCount} to %{targetBranch}.')
: s__(
@@ -48,6 +77,13 @@ export default {
textDecorativeComponent() {
return this.glFeatures.restructuredMrWidget ? 'span' : 'strong';
},
+ squashCommitMessage() {
+ if (this.isMerged) {
+ return s__('mergedCommitsAdded|(commits were squashed)');
+ }
+
+ return n__('(squashes %d commit)', '(squashes %d commits)', this.commitsCount);
+ },
},
mergeCommitCount,
};
@@ -69,9 +105,14 @@ export default {
</template>
<template #squashedCommits>
<template v-if="glFeatures.restructuredMrWidget && isSquashEnabled">
- {{ n__('(squashes %d commit)', '(squashes %d commits)', commitsCount) }}</template
+ {{ squashCommitMessage }}</template
></template
>
+ <template #mergeCommitSha>
+ <template v-if="glFeatures.restructuredMrWidget"
+ ><span class="label-branch">{{ mergeCommitSha }}</span></template
+ >
+ </template>
</gl-sprintf>
</span>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 24cefd63ce3..e7d5e4086bc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -2,8 +2,11 @@
import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
-import { s__ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { s__, __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
+import showToast from '~/vue_shared/plugins/global_toast';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue';
@@ -21,7 +24,7 @@ export default {
ApprovalsSummaryOptional,
GlButton,
},
- mixins: [approvalsMixin],
+ mixins: [approvalsMixin, glFeatureFlagsMixin()],
props: {
mr: {
type: Object,
@@ -171,6 +174,14 @@ export default {
return serviceFn()
.then((data) => {
this.mr.setApprovals(data);
+
+ if (
+ this.glFeatures.mrAttentionRequests &&
+ SidebarMediator.singleton?.store.currentUserHasAttention
+ ) {
+ showToast(__('Approved. Your attention request was removed.'));
+ }
+
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated');
sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
@@ -217,7 +228,7 @@ export default {
<slot
:is-approving="isApproving"
:approve-with-auth="approveWithAuth"
- :hasApproval-auth-error="hasApprovalAuthError"
+ :has-approval-auth-error="hasApprovalAuthError"
></slot>
</template>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index f1b89c42fb5..0bc17de638b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -47,6 +47,8 @@ export default {
fullData: [],
isCollapsed: true,
showFade: false,
+ modalData: undefined,
+ modalName: undefined,
};
},
computed: {
@@ -116,6 +118,9 @@ export default {
return summary;
},
+ modalId() {
+ return this.modalName || `modal${this.$options.name}`;
+ },
},
watch: {
isCollapsed(newVal) {
@@ -249,7 +254,7 @@ export default {
class="media-body gl-display-flex gl-flex-direction-row! gl-align-self-center"
data-testid="widget-extension-top-level"
>
- <div class="gl-flex-grow-1">
+ <div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary">
<template v-if="isLoadingSummary">{{ widgetLoadingText }}</template>
<template v-else-if="hasFetchError">{{ widgetErrorText }}</template>
<div v-else>
@@ -306,12 +311,20 @@ export default {
data-testid="extension-list-item"
>
<gl-intersection-observer
- :options="{ rootMargin: '100px', thresholds: 0.1 }"
+ :options="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ rootMargin: '100px',
+ thresholds: 0.1,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
class="gl-w-full"
@appear="appear(index)"
@disappear="disappear(index)"
>
- <child-content :data="item" :widget-label="widgetLabel" :level="2" />
+ <child-content
+ :data="item"
+ :widget-label="widgetLabel"
+ :modal-id="modalId"
+ :level="2"
+ />
</gl-intersection-observer>
</div>
</dynamic-scroller-item>
@@ -322,5 +335,8 @@ export default {
class="fade mr-extenson-scrim gl-absolute gl-left-0 gl-bottom-0 gl-w-full gl-h-7 gl-pointer-events-none"
></div>
</div>
+ <div v-if="$options.modalComponent && modalData">
+ <component :is="$options.modalComponent" :modal-id="modalId" v-bind="modalData" />
+ </div>
</section>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
index 5cfee21dd5e..0ca4c92a5ae 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
@@ -1,5 +1,5 @@
<script>
-import { GlBadge, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlBadge, GlLink, GlSafeHtmlDirective, GlModalDirective } from '@gitlab/ui';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
import { generateText } from './utils';
@@ -14,6 +14,7 @@ export default {
},
directives: {
SafeHtml: GlSafeHtmlDirective,
+ GlModal: GlModalDirective,
},
props: {
data: {
@@ -24,6 +25,11 @@ export default {
type: String,
required: true,
},
+ modalId: {
+ type: String,
+ required: false,
+ default: null,
+ },
level: {
type: Number,
required: true,
@@ -63,6 +69,11 @@ export default {
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
+ <div v-if="data.modal">
+ <gl-link v-gl-modal="modalId" @click="data.modal.onClick">
+ {{ data.modal.text }}
+ </gl-link>
+ </div>
<div v-if="data.supportingText">
<p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
</div>
@@ -87,6 +98,7 @@ export default {
:key="childData.id"
:data="childData"
:widget-label="widgetLabel"
+ :modal-id="modalId"
:level="3"
data-testid="child-content"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
index 8438f3492b2..65273678fb9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
@@ -14,6 +14,7 @@ export const registerExtension = (extension) => {
i18n: extension.i18n,
expandEvent: extension.expandEvent,
enablePolling: extension.enablePolling,
+ modalComponent: extension.modalComponent,
computed: {
...Object.keys(extension.computed).reduce(
(acc, computedKey) => ({
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue
index 01d8de132e7..456a1f17aae 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue
@@ -49,7 +49,7 @@ export default {
]"
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
>
- <gl-loading-icon v-if="isLoading" size="md" inline class="gl-display-block" />
+ <gl-loading-icon v-if="isLoading" size="lg" inline class="gl-display-block" />
<gl-icon
v-else
:name="$options.EXTENSION_ICON_NAMES[iconName]"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js
index 5fba070f79c..cba12507eba 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/utils.js
@@ -35,9 +35,7 @@ const textStyleTags = {
[getStartTag('small')]: '<span class="gl-font-sm gl-text-gray-700">',
};
-export const generateText = (text) => {
- if (typeof text !== 'string') return null;
-
+const createText = (text) => {
return text
.replace(
new RegExp(
@@ -60,3 +58,21 @@ export const generateText = (text) => {
)
.replace(/%{([a-z]|_)+}/g, ''); // Filter out any tags we don't know about
};
+
+export const generateText = (text) => {
+ if (typeof text === 'string') {
+ return createText(text);
+ } else if (
+ typeof text === 'object' &&
+ typeof text.text === 'string' &&
+ typeof text.href === 'string'
+ ) {
+ return createText(
+ `${
+ text.prependText ? `${text.prependText} ` : ''
+ }<a class="gl-text-decoration-underline" href="${text.href}">${text.text}</a>`,
+ );
+ }
+
+ return null;
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 8cdaa3316ee..e1d88099580 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -1,9 +1,5 @@
<script>
import {
- GlButton,
- GlDropdown,
- GlDropdownSectionHeader,
- GlDropdownItem,
GlLink,
GlTooltipDirective,
GlModalDirective,
@@ -14,8 +10,6 @@ import { constructWebIDEPath } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
-import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
-import MrWidgetHowToMergeModal from './mr_widget_how_to_merge_modal.vue';
import MrWidgetIcon from './mr_widget_icon.vue';
export default {
@@ -24,14 +18,8 @@ export default {
clipboardButton,
TooltipOnTruncate,
MrWidgetIcon,
- MrWidgetHowToMergeModal,
- GlButton,
- GlDropdown,
- GlDropdownSectionHeader,
- GlDropdownItem,
GlLink,
GlSprintf,
- WebIdeLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -107,71 +95,6 @@ export default {
</gl-sprintf>
</div>
</div>
-
- <div class="branch-actions d-flex">
- <template v-if="mr.isOpen">
- <web-ide-link
- v-if="!mr.sourceBranchRemoved"
- :show-edit-button="false"
- :show-web-ide-button="true"
- :web-ide-url="webIdePath"
- :web-ide-text="$options.i18n.webIdeText"
- :show-gitpod-button="mr.showGitpodButton"
- :gitpod-url="mr.gitpodUrl"
- :gitpod-enabled="mr.gitpodEnabled"
- :user-preferences-gitpod-path="mr.userPreferencesGitpodPath"
- :user-profile-enable-gitpod-path="mr.userProfileEnableGitpodPath"
- :gitpod-text="$options.i18n.gitpodText"
- class="gl-display-none gl-md-display-inline-block gl-mr-3"
- data-placement="bottom"
- tabindex="0"
- data-qa-selector="open_in_web_ide_button"
- />
- <gl-button
- v-gl-modal-directive="'modal-merge-info'"
- :disabled="mr.sourceBranchRemoved"
- class="js-check-out-branch gl-mr-3"
- >
- {{ s__('mrWidget|Check out branch') }}
- </gl-button>
- <mr-widget-how-to-merge-modal
- :is-fork="isFork"
- :can-merge="mr.canMerge"
- :source-branch="mr.sourceBranch"
- :source-project="mr.sourceProject"
- :source-project-path="mr.sourceProjectFullPath"
- :target-branch="mr.targetBranch"
- :source-project-default-url="mr.sourceProjectDefaultUrl"
- :reviewing-docs-path="mr.reviewingDocsPath"
- />
- </template>
- <gl-dropdown
- v-gl-tooltip
- :title="__('Download as')"
- :aria-label="__('Download as')"
- icon="download"
- right
- data-qa-selector="download_dropdown"
- >
- <gl-dropdown-section-header>{{ __('Download as') }}</gl-dropdown-section-header>
- <gl-dropdown-item
- :href="mr.emailPatchesPath"
- class="js-download-email-patches"
- download
- data-qa-selector="download_email_patches_menu_item"
- >
- {{ s__('mrWidget|Email patches') }}
- </gl-dropdown-item>
- <gl-dropdown-item
- :href="mr.plainDiffPath"
- class="js-download-plain-diff"
- download
- data-qa-selector="download_plain_diff_menu_item"
- >
- {{ s__('mrWidget|Plain diff') }}
- </gl-dropdown-item>
- </gl-dropdown>
- </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
index e906b8c3b59..c2a3ae361ca 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
@@ -112,11 +112,19 @@ export default {
return escapeShellString(this.sourceBranch);
},
},
+ mounted() {
+ document.addEventListener('click', (e) => {
+ if (e.target.closest('.js-check-out-modal-trigger')) {
+ this.$refs.modal.show();
+ }
+ });
+ },
};
</script>
<template>
<gl-modal
+ ref="modal"
modal-id="modal-merge-info"
:no-enforce-focus="true"
:title="$options.i18n.title"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index c0b80eef082..3b3b46e9772 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -276,12 +276,11 @@ export default {
</div>
</div>
<div>
- <span class="mr-widget-pipeline-graph">
- <span class="stage-cell">
+ <span class="gl-align-items-center gl-display-inline-flex mr-widget-pipeline-graph">
+ <span class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell">
<linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" />
<pipeline-mini-graph
v-if="hasStages"
- class="gl-display-inline-block"
stages-class="mr-widget-pipeline-stages"
:stages="pipeline.details.stages"
:is-merge-train="isMergeTrain"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
index 2cef37d5c2e..b8a1f89d232 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
+import { GlSafeHtmlDirective as SafeHtml, GlLink, GlSprintf } from '@gitlab/ui';
import { s__, n__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -10,6 +10,7 @@ export default {
},
components: {
GlLink,
+ GlSprintf,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -28,6 +29,16 @@ export default {
required: false,
default: true,
},
+ divergedCommitsCount: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ targetBranchPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
closesText() {
@@ -81,5 +92,19 @@ export default {
}}</gl-link>
</span>
</p>
+ <div
+ v-if="
+ divergedCommitsCount > 0 && glFeatures.updatedMrHeader && !glFeatures.restructuredMrWidget
+ "
+ class="diverged-commits-count"
+ >
+ <gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')">
+ <template #link>
+ <gl-link :href="targetBranchPath">{{
+ n__('%d commit behind', '%d commits behind', divergedCommitsCount)
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
</section>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 9499603163b..7ff1eb6e73a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -46,7 +46,7 @@ export default {
<gl-button
v-if="!glFeatures.restructuredMrWidget && showDisabledButton"
category="primary"
- variant="success"
+ variant="confirm"
data-testid="disabled-merge-button"
:disabled="true"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index a44caf886a4..aabbeac564a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -175,7 +175,7 @@ export default {
{{ cancelButtonText }}
</gl-button>
</h4>
- <section class="mr-info-list">
+ <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p v-if="shouldRemoveSourceBranch">
{{ s__('mrWidget|Deletes the source branch') }}
</p>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
index 6d5ca58aa20..d50e52f5ac1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
@@ -1,4 +1,5 @@
<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -8,6 +9,7 @@ export default {
MrWidgetAuthorTime,
statusIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
/* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
@@ -29,7 +31,7 @@ export default {
:date-readable="mr.metrics.readableClosedAt"
/>
- <section class="mr-info-list">
+ <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p>
{{ s__('mrWidget|The changes were not merged into') }}
<a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 7435f578852..def30dacf8a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlModalDirective, GlSkeletonLoader } from '@gitlab/ui';
+import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import userPermissionsQuery from '../../queries/permissions.query.graphql';
@@ -13,9 +13,6 @@ export default {
StatusIcon,
GlButton,
},
- directives: {
- GlModalDirective,
- },
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
userPermissions: {
@@ -131,9 +128,9 @@ export default {
</gl-button>
<gl-button
v-if="canMerge"
- v-gl-modal-directive="'modal-merge-info'"
:size="glFeatures.restructuredMrWidget ? 'small' : 'medium'"
data-testid="merge-locally-button"
+ class="js-check-out-modal-trigger"
>
{{ s__('mrWidget|Resolve locally') }}
</gl-button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 84dac95ce74..bf036f562ed 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,6 +1,7 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import api from '~/api';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
@@ -22,6 +23,7 @@ export default {
GlLoadingIcon,
GlButton,
},
+ mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@@ -181,7 +183,11 @@ export default {
{{ s__('mrWidget|Delete source branch') }}
</gl-button>
</div>
- <section class="mr-info-list" data-qa-selector="merged_status_content">
+ <section
+ v-if="!glFeatures.restructuredMrWidget"
+ class="mr-info-list"
+ data-qa-selector="merged_status_content"
+ >
<p>
{{ s__('mrWidget|The changes were merged into') }}
<span class="label-branch">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
index 887d1aab524..b86ab69af3f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
@@ -1,5 +1,6 @@
<script>
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '~/merge_request';
import eventHub from '../../event_hub';
@@ -14,6 +15,7 @@ export default {
components: {
statusIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@@ -88,7 +90,7 @@ export default {
{{ mergeStatus.message }}
<gl-emoji :data-name="mergeStatus.emoji" />
</h4>
- <section class="mr-info-list">
+ <section v-if="!glFeatures.restructuredMrWidget" class="mr-info-list">
<p>
{{ s__('mrWidget|Merges changes into') }}
<span class="label-branch">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 4f8faeb877f..4fb95fe635c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -322,11 +322,33 @@ export default {
},
restructuredWidgetShowMergeButtons() {
if (this.glFeatures.restructuredMrWidget) {
- return this.isMergeAllowed && this.state.userPermissions.canMerge;
+ return (
+ (this.isMergeAllowed || this.isAutoMergeAvailable) &&
+ this.state.userPermissions.canMerge &&
+ !this.mr.mergeOngoing &&
+ !this.mr.autoMergeEnabled
+ );
}
return true;
},
+ sourceBranchDeletedText() {
+ if (this.glFeatures.restructuredMrWidget) {
+ if (this.removeSourceBranch) {
+ return this.mr.state === 'merged'
+ ? __('Deleted the source branch.')
+ : __('Source branch will be deleted.');
+ }
+
+ return this.mr.state === 'merged'
+ ? __('Did not delete the source branch.')
+ : __('Source branch will not be deleted.');
+ }
+
+ return this.removeSourceBranch
+ ? __('Deletes the source branch.')
+ : __('Does not delete the source branch.');
+ },
},
mounted() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
@@ -421,6 +443,8 @@ export default {
if (this.glFeatures.mergeRequestWidgetGraphql) {
this.updateGraphqlState();
}
+
+ this.isMakingRequest = false;
})
.catch(() => {
this.isMakingRequest = false;
@@ -499,6 +523,7 @@ export default {
<template>
<div
+ data-testid="ready_to_merge_state"
:class="{
'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base':
glFeatures.restructuredMrWidget,
@@ -611,6 +636,7 @@ export default {
glFeatures.restructuredMrWidget && (shouldShowSquashEdit || shouldShowMergeEdit)
"
v-model="editCommitMessage"
+ data-testid="widget_edit_commit_message"
class="gl-display-flex gl-align-items-center"
>
{{ __('Edit commit message') }}
@@ -686,25 +712,36 @@ export default {
v-if="!restructuredWidgetShowMergeButtons"
class="gl-w-full gl-order-n1 gl-text-gray-500"
>
- <strong>
+ <strong v-if="mr.state !== 'closed'">
{{ __('Merge details') }}
</strong>
<ul class="gl-pl-4 gl-m-0">
+ <li
+ v-if="mr.divergedCommitsCount > 0 && glFeatures.updatedMrHeader"
+ class="gl-line-height-normal"
+ >
+ <gl-sprintf
+ :message="s__('mrWidget|The source branch is %{link} the target branch')"
+ >
+ <template #link>
+ <gl-link :href="mr.targetBranchPath">{{
+ n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount)
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </li>
<li class="gl-line-height-normal">
<added-commit-message
+ :state="mr.state"
+ :merge-commit-sha="mr.shortMergeCommitSha"
:is-squash-enabled="squashBeforeMerge"
:is-fast-forward-enabled="!shouldShowMergeEdit"
:commits-count="commitsCount"
:target-branch="stateData.targetBranch"
/>
</li>
- <li class="gl-line-height-normal">
- <template v-if="removeSourceBranch">
- {{ __('Deletes the source branch.') }}
- </template>
- <template v-else>
- {{ __('Does not delete the source branch.') }}
- </template>
+ <li v-if="mr.state !== 'closed'" class="gl-line-height-normal">
+ {{ sourceBranchDeletedText }}
</li>
<li v-if="mr.relatedLinks" class="gl-line-height-normal">
<related-links
@@ -733,6 +770,8 @@ export default {
:state="mr.state"
:related-links="mr.relatedLinks"
:show-assign-to-me="false"
+ :diverged-commits-count="mr.divergedCommitsCount"
+ :target-branch-path="mr.targetBranchPath"
class="mr-ready-merge-related-links gl-display-inline"
/>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
index d32db50874c..cea8df2484b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
@@ -83,14 +83,11 @@ export default {
this.collapsedData.newErrors.map((e) => {
return fullData.push({
text: `${capitalizeFirstCharacter(e.severity)} - ${e.description}`,
- subtext: sprintf(
- s__(`ciReport|in %{open_link}${e.file_path}:${e.line}%{close_link}`),
- {
- open_link: `<a class="gl-text-decoration-underline" href="${e.urlPath}">`,
- close_link: '</a>',
- },
- false,
- ),
+ subtext: {
+ prependText: s__(`ciReport|in`),
+ text: `${e.file_path}:${e.line}`,
+ href: e.urlPath,
+ },
icon: {
name: SEVERITY_ICONS_EXTENSION[e.severity],
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js
index cd5cfb6837c..23f14bea4e1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/constants.js
@@ -10,6 +10,8 @@ export const i18n = {
label: s__('Reports|Test summary'),
loading: s__('Reports|Test summary results are loading'),
error: s__('Reports|Test summary failed to load results'),
+ newHeader: s__('Reports|New'),
+ fixedHeader: s__('Reports|Fixed'),
fullReport: s__('Reports|Full report'),
noChanges: (bold) => s__(`Reports|${noText(bold)} changed test results`),
@@ -36,4 +38,32 @@ export const i18n = {
sprintf(s__('Reports|An error occurred while loading %{name} results'), { name }),
headReportParsingError: s__('Reports|Head report parsing error:'),
baseReportParsingError: s__('Reports|Base report parsing error:'),
+
+ recentFailureSummary: (recentlyFailed, failed) => {
+ if (failed < 2) {
+ return sprintf(
+ s__(
+ 'Reports|%{recentlyFailed} out of %{failed} failed test has failed more than once in the last 14 days',
+ ),
+ { recentlyFailed, failed },
+ );
+ }
+ return sprintf(
+ n__(
+ 'Reports|%{recentlyFailed} out of %{failed} failed tests has failed more than once in the last 14 days',
+ 'Reports|%{recentlyFailed} out of %{failed} failed tests have failed more than once in the last 14 days',
+ recentlyFailed,
+ ),
+ { recentlyFailed, failed },
+ );
+ },
+ recentFailureCount: (recentFailures) =>
+ sprintf(
+ n__(
+ 'Reports|Failed %{count} time in %{base_branch} in the last 14 days',
+ 'Reports|Failed %{count} times in %{base_branch} in the last 14 days',
+ recentFailures.count,
+ ),
+ recentFailures,
+ ),
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
index 65d9257903f..577b2cbfc5c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
@@ -1,7 +1,13 @@
import { uniqueId } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { EXTENSION_ICONS } from '../../constants';
-import { summaryTextBuilder, reportTextBuilder, reportSubTextBuilder } from './utils';
+import {
+ summaryTextBuilder,
+ reportTextBuilder,
+ reportSubTextBuilder,
+ countRecentlyFailedTests,
+ recentFailuresTextBuilder,
+} from './utils';
import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
export default {
@@ -18,7 +24,10 @@ export default {
if (data.hasSuiteError) {
return this.$options.i18n.error;
}
- return summaryTextBuilder(this.$options.i18n.label, data.summary);
+ return {
+ subject: summaryTextBuilder(this.$options.i18n.label, data.summary),
+ meta: recentFailuresTextBuilder(data.summary),
+ };
},
statusIcon(data) {
if (data.parsingInProgress) {
@@ -50,6 +59,10 @@ export default {
hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS),
parsingInProgress: status === 204,
...data,
+ summary: {
+ recentlyFailed: countRecentlyFailedTests(data.suites),
+ ...data.summary,
+ },
},
};
});
@@ -66,17 +79,66 @@ export default {
}
return EXTENSION_ICONS.success;
},
- prepareReports() {
- return this.collapsedData.suites.map((suite) => {
+ testHeader(test, sectionHeader, index) {
+ const headers = [];
+ if (index === 0) {
+ headers.push(sectionHeader);
+ }
+ if (test.recent_failures?.count && test.recent_failures?.base_branch) {
+ headers.push(i18n.recentFailureCount(test.recent_failures));
+ }
+ return headers;
+ },
+ mapTestAsChild({ iconName, sectionHeader }) {
+ return (test, index) => {
return {
- id: uniqueId('suite-'),
- text: reportTextBuilder(suite),
- subtext: reportSubTextBuilder(suite),
- icon: {
- name: this.suiteIcon(suite),
- },
+ id: uniqueId('test-'),
+ header: this.testHeader(test, sectionHeader, index),
+ icon: { name: iconName },
+ text: test.name,
};
- });
+ };
+ },
+ prepareReports() {
+ return this.collapsedData.suites
+ .map((suite) => {
+ return {
+ ...suite,
+ summary: {
+ recentlyFailed: countRecentlyFailedTests(suite),
+ ...suite.summary,
+ },
+ };
+ })
+ .map((suite) => {
+ return {
+ id: uniqueId('suite-'),
+ text: reportTextBuilder(suite),
+ subtext: reportSubTextBuilder(suite),
+ icon: {
+ name: this.suiteIcon(suite),
+ },
+ children: [
+ ...[...suite.new_failures, ...suite.new_errors].map(
+ this.mapTestAsChild({
+ sectionHeader: i18n.newHeader,
+ iconName: EXTENSION_ICONS.failed,
+ }),
+ ),
+ ...[...suite.existing_failures, ...suite.existing_errors].map(
+ this.mapTestAsChild({
+ iconName: EXTENSION_ICONS.failed,
+ }),
+ ),
+ ...[...suite.resolved_failures, ...suite.resolved_errors].map(
+ this.mapTestAsChild({
+ sectionHeader: i18n.fixedHeader,
+ iconName: EXTENSION_ICONS.success,
+ }),
+ ),
+ ],
+ };
+ });
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
index a74ed20362f..9e4b0ac581c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
@@ -43,13 +43,42 @@ export const reportTextBuilder = ({ name = '', summary = {}, status }) => {
return i18n.summaryText(name, resultsString);
};
-export const reportSubTextBuilder = ({ suite_errors }) => {
- const errors = [];
- if (suite_errors?.head) {
- errors.push(`${i18n.headReportParsingError} ${suite_errors.head}`);
- }
- if (suite_errors?.base) {
- errors.push(`${i18n.baseReportParsingError} ${suite_errors.base}`);
+export const recentFailuresTextBuilder = (summary = {}) => {
+ const { failed, recentlyFailed } = summary;
+ if (!failed || !recentlyFailed) return '';
+
+ return i18n.recentFailureSummary(recentlyFailed, failed);
+};
+
+export const reportSubTextBuilder = ({ suite_errors, summary }) => {
+ if (suite_errors?.head || suite_errors?.base) {
+ const errors = [];
+ if (suite_errors?.head) {
+ errors.push(`${i18n.headReportParsingError} ${suite_errors.head}`);
+ }
+ if (suite_errors?.base) {
+ errors.push(`${i18n.baseReportParsingError} ${suite_errors.base}`);
+ }
+ return errors.join('<br />');
}
- return errors.join('<br />');
+ return recentFailuresTextBuilder(summary);
+};
+
+export const countRecentlyFailedTests = (subject) => {
+ // handle either a single report or an array of reports
+ const reports = !subject.length ? [subject] : subject;
+
+ return reports
+ .map((report) => {
+ return (
+ [report.new_failures, report.existing_failures, report.resolved_failures]
+ // only count tests which have failed more than once
+ .map(
+ (failureArray) =>
+ failureArray.filter((failure) => failure.recent_failures?.count > 1).length,
+ )
+ .reduce((total, count) => total + count, 0)
+ );
+ })
+ .reduce((total, count) => total + count, 0);
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 4b3ad288768..8ebb7f6f159 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -165,7 +165,10 @@ export default {
return this.mr?.codequalityReportsPath;
},
shouldRenderRelatedLinks() {
- return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
+ return (
+ (Boolean(this.mr.relatedLinks) || this.mr.divergedCommitsCount > 0) &&
+ !this.mr.isNothingToMergeState
+ );
},
shouldRenderSourceBranchRemovalStatus() {
return (
@@ -195,6 +198,9 @@ export default {
shouldRenderTestReport() {
return Boolean(this.mr?.testResultsPath);
},
+ shouldRenderRefactoredTestReport() {
+ return window.gon?.features?.refactorMrWidgetTestSummary;
+ },
mergeError() {
let { mergeError } = this.mr;
@@ -228,6 +234,9 @@ export default {
isRestructuredMrWidgetEnabled() {
return window.gon?.features?.restructuredMrWidget;
},
+ isUpdatedHeaderEnabled() {
+ return window.gon?.features?.updatedMrHeader;
+ },
},
watch: {
'mr.machineValue': {
@@ -512,7 +521,7 @@ export default {
}
},
registerTestReportExtension() {
- if (this.shouldRenderTestReport && this.shouldShowExtension) {
+ if (this.shouldRenderTestReport && this.shouldRenderRefactoredTestReport) {
registerExtension(testReportExtension);
}
},
@@ -521,11 +530,15 @@ export default {
</script>
<template>
<div v-if="isLoaded" class="mr-state-widget gl-mt-3">
- <header class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100">
+ <header
+ v-if="shouldRenderCollaborationStatus || !isUpdatedHeaderEnabled"
+ :class="{ 'mr-widget-workflow gl-mt-0!': isUpdatedHeaderEnabled }"
+ class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100 gl-overflow-hidden"
+ >
<mr-widget-alert-message v-if="shouldRenderCollaborationStatus" type="info">
{{ s__('mrWidget|Members who can merge are allowed to add commits.') }}
</mr-widget-alert-message>
- <mr-widget-header :mr="mr" />
+ <mr-widget-header v-if="!isUpdatedHeaderEnabled" :mr="mr" />
</header>
<mr-widget-suggest-pipeline
v-if="shouldSuggestPipelines"
@@ -588,7 +601,7 @@ export default {
/>
<grouped-test-reports-app
- v-if="mr.testResultsPath && !shouldShowExtension"
+ v-if="shouldRenderTestReport && !shouldRenderRefactoredTestReport"
class="js-reports-container"
:endpoint="mr.testResultsPath"
:head-blob-path="mr.headBlobPath"
@@ -617,6 +630,8 @@ export default {
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
+ :diverged-commits-count="mr.divergedCommitsCount"
+ :target-branch-path="mr.targetBranchPath"
class="mr-info-list gl-ml-7 gl-pb-5"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql
index 99e6f4e9beb..efc0673bc26 100644
--- a/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/ready_to_merge.fragment.graphql
@@ -11,7 +11,6 @@ fragment ReadyToMerge on Project {
shouldRemoveSourceBranch
forceRemoveSourceBranch
defaultMergeCommitMessage
- defaultMergeCommitMessageWithDescription
defaultSquashCommitMessage
squash
squashOnMerge
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index eb07609d5d6..146cf7e11a7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -1,5 +1,5 @@
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
-import { statusBoxState } from '~/issuable/components/status_box.vue';
+import { badgeState } from '~/issuable/components/status_box.vue';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { machine } from '~/lib/utils/finite_state_machine';
import {
@@ -221,8 +221,8 @@ export default class MergeRequestStore {
}
updateStatusState(state) {
- if (this.mergeRequestState !== state && statusBoxState.updateStatus) {
- statusBoxState.updateStatus();
+ if (this.mergeRequestState !== state && badgeState.updateStatus) {
+ badgeState.updateStatus();
}
}
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
index 948d2505966..c93f620995f 100644
--- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
+++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue
@@ -278,7 +278,7 @@ export default {
data-testid="viewIncidentBtn"
:href="incidentPath(alert.issue.iid)"
category="primary"
- variant="success"
+ variant="confirm"
>
{{ s__('AlertManagement|View incident') }}
</gl-button>
@@ -288,7 +288,7 @@ export default {
data-testid="createIncidentBtn"
:loading="incidentCreationInProgress"
category="primary"
- variant="success"
+ variant="confirm"
@click="createIncident()"
>
{{ s__('AlertManagement|Create incident') }}
diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
index 8b76af05ffe..6a03e38a31d 100644
--- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
@@ -1,12 +1,12 @@
<script>
-import { GlSegmentedControl } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
+import SegmentedControlButtonGroup from '~/vue_shared/components/segmented_control_button_group.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
export default {
components: {
- GlSegmentedControl,
CiCdAnalyticsAreaChart,
+ SegmentedControlButtonGroup,
},
props: {
charts: {
@@ -38,7 +38,11 @@ export default {
</script>
<template>
<div>
- <gl-segmented-control v-model="selectedChart" :options="chartRanges" class="gl-mb-4" />
+ <segmented-control-button-group
+ v-model="selectedChart"
+ :options="chartRanges"
+ class="gl-mb-4"
+ />
<ci-cd-analytics-area-chart
v-if="chart"
v-bind="$attrs"
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 07bd6019b80..9bccc49e894 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -11,17 +11,22 @@ import { GlIcon } from '@gitlab/ui';
* }
*
* Used in:
- * - Pipelines table Badge
- * - Pipelines table mini graph
- * - Pipeline graph
- * - Pipeline show view badge
- * - Jobs table
+ * - Extended MR Popover
* - Jobs show view header
* - Jobs show view sidebar
+ * - Jobs table
* - Linked pipelines
- * - Extended MR Popover
+ * - Pipeline graph
+ * - Pipeline mini graph
+ * - Pipeline show view badge
+ * - Pipelines table Badge
+ */
+
+/*
+ * These sizes are defined in gitlab-ui/src/scss/variables.scss
+ * under '$gl-icon-sizes'
*/
-const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
+const validSizes = [8, 12, 14, 16, 24, 32, 48, 72];
export default {
components: {
@@ -45,6 +50,11 @@ export default {
required: false,
default: false,
},
+ isInteractive: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
cssClasses: {
type: String,
required: false,
@@ -52,9 +62,9 @@ export default {
},
},
computed: {
- cssClass() {
+ wrapperStyleClasses() {
const status = this.status.group;
- return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
+ return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center`;
},
icon() {
return this.borderless ? `${this.status.icon}_borderless` : this.status.icon;
@@ -63,7 +73,10 @@ export default {
};
</script>
<template>
- <span :class="cssClass">
+ <span
+ :class="[wrapperStyleClasses, { interactive: isInteractive }]"
+ :style="{ height: `${size}px`, width: `${size}px` }"
+ >
<gl-icon :name="icon" :size="size" :class="cssClasses" :aria-label="status.icon" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue b/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue
index 7a166f9a3e4..78db2bf15b0 100644
--- a/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue
@@ -4,6 +4,7 @@
*
* @example
* <color-picker
+ :id="example-id"
:invalid-feedback="__('Please enter a valid hex (#RRGGBB or #RGB) color value')"
:label="__('Background color')"
:value="#FF0000"
@@ -12,6 +13,7 @@
/>
*/
import { GlFormGroup, GlFormInput, GlFormInputGroup, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
import { __ } from '~/locale';
const PREVIEW_COLOR_DEFAULT_CLASSES =
@@ -29,6 +31,11 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
+ id: {
+ type: String,
+ required: false,
+ default: () => uniqueId('color-picker-'),
+ },
invalidFeedback: {
type: String,
required: false,
@@ -94,14 +101,13 @@ export default {
<div>
<gl-form-group
:label="label"
- label-for="color-picker"
+ :label-for="id"
:description="description"
:invalid-feedback="invalidFeedback"
:state="state"
:class="{ 'gl-mb-3!': hasSuggestedColors }"
>
<gl-form-input-group
- id="color-picker"
max-length="7"
type="text"
class="gl-align-center gl-rounded-0 gl-rounded-top-right-base gl-rounded-bottom-right-base"
@@ -112,6 +118,7 @@ export default {
<template #prepend>
<div :class="previewColorClasses" :style="previewColor" data-testid="color-preview">
<gl-form-input
+ :id="id"
type="color"
class="gl-absolute gl-top-0 gl-left-0 gl-h-full! gl-p-0! gl-m-0! gl-opacity-0"
tabindex="-1"
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ebbc1bfb037..388353bc35b 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -172,7 +172,9 @@ export default {
:img-src="author.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="author.username"
+ :img-size="16"
class="avatar-image-container text-decoration-none"
+ img-css-classes="gl-mr-3"
/>
<tooltip-on-truncate :title="title" class="flex-truncate-child">
<gl-link :href="commitUrl" class="commit-row-message cgray">{{ title }}</gl-link>
diff --git a/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue b/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue
new file mode 100644
index 00000000000..298c7bc50cc
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue
@@ -0,0 +1,39 @@
+<script>
+import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
+import { confidentialityInfoText } from '../constants';
+
+export default {
+ components: {
+ GlBadge,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ workspaceType: {
+ type: String,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ confidentialTooltip() {
+ return confidentialityInfoText(this.workspaceType, this.issuableType);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-badge
+ v-gl-tooltip.bottom
+ :title="confidentialTooltip"
+ icon="eye-slash"
+ variant="warning"
+ class="gl-display-inline gl-mr-2"
+ >{{ __('Confidential') }}</gl-badge
+ >
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
index 6629b293eb9..8481280f25f 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
@@ -14,6 +14,8 @@ const Template = (args, { argTypes }) => ({
additionalInformation: args.additionalInformation || null,
confirmDangerMessage: args.confirmDangerMessage || 'You require more Vespene Gas',
htmlConfirmationMessage: args.confirmDangerMessage || false,
+ confirmButtonText: args.confirmButtonText || 'Cancel',
+ cancelButtonText: args.cancelButtonText || 'Confirm',
},
});
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
index 88890b3332d..37e480f7e41 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
@@ -12,7 +12,7 @@ import {
CONFIRM_DANGER_MODAL_TITLE,
CONFIRM_DANGER_PHRASE_TEXT,
CONFIRM_DANGER_WARNING,
- CONFIRM_DANGER_MODAL_ERROR,
+ CONFIRM_DANGER_MODAL_CANCEL,
} from './constants';
export default {
@@ -40,6 +40,9 @@ export default {
additionalInformation: {
default: CONFIRM_DANGER_WARNING,
},
+ cancelButtonText: {
+ default: CONFIRM_DANGER_MODAL_CANCEL,
+ },
},
props: {
modalId: {
@@ -66,6 +69,11 @@ export default {
attributes: [{ variant: 'danger', disabled: !this.isValid, class: 'qa-confirm-button' }],
};
},
+ actionCancel() {
+ return {
+ text: this.cancelButtonText,
+ };
+ },
},
methods: {
equalString(a, b) {
@@ -77,7 +85,6 @@ export default {
CONFIRM_DANGER_MODAL_TITLE,
CONFIRM_DANGER_WARNING,
CONFIRM_DANGER_PHRASE_TEXT,
- CONFIRM_DANGER_MODAL_ERROR,
},
};
</script>
@@ -88,6 +95,7 @@ export default {
:data-testid="modalId"
:title="$options.i18n.CONFIRM_DANGER_MODAL_TITLE"
:action-primary="actionPrimary"
+ :action-cancel="actionCancel"
@primary="$emit('confirm')"
>
<gl-alert
@@ -110,7 +118,7 @@ export default {
</template>
</gl-sprintf>
</p>
- <gl-form-group :state="isValid" :invalid-feedback="$options.i18n.CONFIRM_DANGER_MODAL_ERROR">
+ <gl-form-group :state="isValid">
<gl-form-input
id="confirm_name_input"
v-model="confirmationPhrase"
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js b/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js
index fa44a9be411..90d55d0f93f 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js
@@ -2,7 +2,6 @@ import { __ } from '~/locale';
export const CONFIRM_DANGER_MODAL_ID = 'confirm-danger-modal';
export const CONFIRM_DANGER_MODAL_TITLE = __('Confirmation required');
-export const CONFIRM_DANGER_MODAL_ERROR = __('Confirmation required');
export const CONFIRM_DANGER_MODAL_BUTTON = __('Confirm');
export const CONFIRM_DANGER_WARNING = __(
'This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.',
@@ -10,3 +9,4 @@ export const CONFIRM_DANGER_WARNING = __(
export const CONFIRM_DANGER_PHRASE_TEXT = __(
'Please type %{phrase_code} to proceed or close this modal to cancel.',
);
+export const CONFIRM_DANGER_MODAL_CANCEL = __('Cancel');
diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
index e546ca57c5e..181c1b89e31 100644
--- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue
@@ -248,7 +248,7 @@ export default {
__('Cancel')
}}</gl-button>
<gl-button
- variant="success"
+ variant="confirm"
category="primary"
:disabled="!isValid"
@click="setFixedRange()"
diff --git a/app/assets/javascripts/vue_shared/components/deployment_instance.vue b/app/assets/javascripts/vue_shared/components/deployment_instance.vue
index 41b783aa011..4aae86fc82b 100644
--- a/app/assets/javascripts/vue_shared/components/deployment_instance.vue
+++ b/app/assets/javascripts/vue_shared/components/deployment_instance.vue
@@ -14,6 +14,7 @@
*/
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -22,7 +23,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
-
+ mixins: [glFeatureFlagsMixin()],
props: {
/**
* Represents the status of the pod. Each state is represented with a different
@@ -75,7 +76,9 @@ export default {
},
computedLogPath() {
- return this.isLink ? mergeUrlParams({ pod_name: this.podName }, this.logsPath) : null;
+ return this.isLink && this.glFeatures.monitorLogging
+ ? mergeUrlParams({ pod_name: this.podName }, this.logsPath)
+ : null;
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue
deleted file mode 100644
index afde0c81580..00000000000
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
-export default {
- props: {
- name: {
- type: String,
- required: true,
- },
- value: {
- type: [Number, String],
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <input :name="name" :value="value" type="hidden" />
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue
deleted file mode 100644
index edb5ffdc39c..00000000000
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlIcon,
- },
- props: {
- placeholderText: {
- type: String,
- required: true,
- default: __('Search'),
- },
- focused: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- return { searchQuery: this.value };
- },
- watch: {
- searchQuery(query) {
- this.$emit('input', query);
- },
- focused(val) {
- if (val) {
- this.$refs.searchInput.focus();
- }
- },
- },
-};
-</script>
-
-<template>
- <div class="dropdown-input">
- <input
- ref="searchInput"
- v-model="searchQuery"
- :placeholder="placeholderText"
- class="dropdown-input-field"
- type="search"
- autocomplete="off"
- />
- <gl-icon name="search" class="dropdown-input-search" data-hidden="true" />
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
index 2a79ccc2648..840911dc99c 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue
@@ -1,28 +1,22 @@
<script>
import {
- GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
- GlDropdownSectionHeader,
GlSearchBoxByType,
} from '@gitlab/ui';
import { __ } from '~/locale';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
export default {
components: {
- GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
- GlDropdownSectionHeader,
GlSearchBoxByType,
- TooltipOnTruncate,
},
props: {
selectText: {
@@ -45,11 +39,6 @@ export default {
required: false,
default: () => [],
},
- groupedOptions: {
- type: Array,
- required: false,
- default: () => [],
- },
isLoading: {
type: Boolean,
required: false,
@@ -70,6 +59,11 @@ export default {
required: false,
default: false,
},
+ customIsSelectedOption: {
+ type: Function,
+ required: false,
+ default: undefined,
+ },
},
computed: {
isSearchEmpty() {
@@ -87,6 +81,9 @@ export default {
}
},
isSelected(option) {
+ if (this.customIsSelectedOption !== undefined) {
+ return this.customIsSelectedOption(option);
+ }
if (Array.isArray(this.selected)) {
return this.selected.some((label) => label.title === option.title);
}
@@ -143,7 +140,7 @@ export default {
<gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content">
<gl-loading-icon
v-if="isLoading"
- size="md"
+ size="lg"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>
@@ -177,36 +174,7 @@ export default {
{{ option.title }}
</slot>
</gl-dropdown-item>
- <template v-for="(optionGroup, index) in groupedOptions">
- <gl-dropdown-divider v-if="index !== 0" :key="index" />
- <gl-dropdown-section-header :key="optionGroup.id">
- <div class="gl-display-flex gl-max-w-full">
- <tooltip-on-truncate
- :title="optionGroup.title"
- class="gl-text-truncate gl-flex-grow-1"
- >
- {{ optionGroup.title }}
- </tooltip-on-truncate>
- <span v-if="optionGroup.secondaryText" class="gl-float-right gl-font-weight-normal">
- <gl-icon name="clock" class="gl-mr-2" />
- {{ optionGroup.secondaryText }}
- </span>
- </div>
- </gl-dropdown-section-header>
- <gl-dropdown-item
- v-for="option in optionGroup.options"
- :key="optionKey(option)"
- :is-checked="isSelected(option)"
- is-check-centered
- is-check-item
- data-testid="unselected-option"
- @click="selectOption(option)"
- >
- <slot name="item" :item="option">
- {{ option.title }}
- </slot>
- </gl-dropdown-item>
- </template>
+ <slot v-bind="{ isSelected }" name="grouped-options"></slot>
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
{{ $options.i18n.noMatchingResults }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
index b0c1c1531aa..680f229d5e8 100644
--- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue
+++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import Mousetrap from 'mousetrap';
import VirtualList from 'vue-virtual-scroll-list';
@@ -9,13 +9,13 @@ import Item from './item.vue';
export const MAX_FILE_FINDER_RESULTS = 40;
export const FILE_FINDER_ROW_HEIGHT = 55;
-export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33;
const originalStopCallback = Mousetrap.prototype.stopCallback;
export default {
components: {
GlIcon,
+ GlLoadingIcon,
Item,
VirtualList,
},
@@ -71,7 +71,7 @@ export default {
return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1;
},
listHeight() {
- return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT;
+ return FILE_FINDER_ROW_HEIGHT;
},
showClearInputButton() {
return this.searchText.trim() !== '';
@@ -265,9 +265,9 @@ export default {
</li>
</template>
<li v-else class="dropdown-menu-empty-item">
- <div class="gl-mr-3 gl-ml-3 gl-mt-3 gl-mb-3">
+ <div class="gl-mr-3 gl-ml-3 gl-mt-5 gl-mb-3">
<template v-if="loading">
- {{ __('Loading...') }}
+ <gl-loading-icon />
</template>
<template v-else>
{{ __('No files found.') }}
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
index 3d48c74b40b..d7a84798e47 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
@@ -1,6 +1,6 @@
import { __ } from '~/locale';
-export const DEBOUNCE_DELAY = 200;
+export const DEBOUNCE_DELAY = 500;
export const MAX_RECENT_TOKENS_SIZE = 3;
export const FILTER_NONE = 'None';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 6638a5de62f..33d507dad57 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -89,32 +89,20 @@ export default {
required: false,
default: () => ({}),
},
+ syncFilterAndSort: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
- let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending;
- let selectedSortDirection = SortDirection.descending;
-
- // Extract correct sortBy value based on initialSortBy
- if (this.initialSortBy) {
- selectedSortOption = this.sortOptions
- .filter(
- (sortBy) =>
- sortBy.sortDirection.ascending === this.initialSortBy ||
- sortBy.sortDirection.descending === this.initialSortBy,
- )
- .pop();
- selectedSortDirection = Object.keys(selectedSortOption.sortDirection).find(
- (key) => selectedSortOption.sortDirection[key] === this.initialSortBy,
- );
- }
-
return {
initialRender: true,
recentSearchesPromise: null,
recentSearches: [],
filterValue: this.initialFilterValue,
- selectedSortOption,
- selectedSortDirection,
+ selectedSortOption: this.sortOptions[0],
+ selectedSortDirection: SortDirection.descending,
};
},
computed: {
@@ -173,7 +161,20 @@ export default {
return undefined;
},
},
+ watch: {
+ initialFilterValue(newValue) {
+ if (this.syncFilterAndSort) {
+ this.filterValue = newValue;
+ }
+ },
+ initialSortBy(newValue) {
+ if (this.syncFilterAndSort) {
+ this.updateSelectedSortValues(newValue);
+ }
+ },
+ },
created() {
+ this.updateSelectedSortValues(this.initialSortBy);
if (this.recentSearchesStorageKey) this.setupRecentSearch();
},
methods: {
@@ -309,12 +310,25 @@ export default {
const cleared = true;
this.$emit('onFilter', [], cleared);
},
+ updateSelectedSortValues(sort) {
+ if (!sort) {
+ return;
+ }
+
+ this.selectedSortOption = this.sortOptions.find(
+ (sortBy) =>
+ sortBy.sortDirection.ascending === sort || sortBy.sortDirection.descending === sort,
+ );
+ this.selectedSortDirection = Object.keys(this.selectedSortOption.sortDirection).find(
+ (key) => this.selectedSortOption.sortDirection[key] === sort,
+ );
+ },
},
};
</script>
<template>
- <div class="vue-filtered-search-bar-container d-md-flex">
+ <div class="vue-filtered-search-bar-container gl-md-display-flex">
<gl-form-checkbox
v-if="showCheckbox"
class="gl-align-self-center"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index 696456be990..848c49c48c7 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -87,6 +87,7 @@ export default {
:get-active-token-value="getActiveAuthor"
:default-suggestions="defaultAuthors"
:preloaded-suggestions="preloadedAuthors"
+ v-bind="$attrs"
@fetch-suggestions="fetchAuthors"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
index e7923e0b55e..c3a0a97a7ba 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -211,10 +211,22 @@ export default {
@select="handleTokenValueSelected"
>
<template #view-token="viewTokenProps">
- <slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
+ <slot
+ name="view-token"
+ :view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ ...viewTokenProps,
+ activeTokenValue,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ ></slot>
</template>
<template #view="viewTokenProps">
- <slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
+ <slot
+ name="view"
+ :view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ ...viewTokenProps,
+ activeTokenValue,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ ></slot>
</template>
<template v-if="suggestionsEnabled" #suggestions>
<template v-if="showDefaultSuggestions">
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
index 4ecfc1cf40c..aa5161ca93c 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
@@ -65,6 +65,7 @@ export default {
:suggestions="branches"
:suggestions-loading="loading"
:get-active-token-value="getActiveBranch"
+ v-bind="$attrs"
@fetch-suggestions="fetchBranches"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
index 5a69751a2cc..210d814d22a 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
@@ -67,6 +67,7 @@ export default {
:suggestions="emojis"
:suggestions-loading="loading"
:get-active-token-value="getActiveEmoji"
+ v-bind="$attrs"
@fetch-suggestions="fetchEmojis"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index 3f7a8920f48..6f24955814c 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -104,6 +104,7 @@ export default {
:suggestions="labels"
:get-active-token-value="getActiveLabel"
:default-suggestions="defaultLabels"
+ v-bind="$attrs"
@fetch-suggestions="fetchLabels"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
index 11c081ab4f8..69265d0fdc9 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
@@ -84,6 +84,7 @@ export default {
:suggestions="milestones"
:suggestions-loading="loading"
:get-active-token-value="getActiveMilestone"
+ v-bind="$attrs"
@fetch-suggestions="fetchMilestones"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
index f353cc3a765..9e68c92af5d 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
@@ -66,6 +66,7 @@ export default {
:suggestions="releases"
:suggestions-loading="loading"
:get-active-token-value="getActiveRelease"
+ v-bind="$attrs"
@fetch-suggestions="fetchReleases"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 722df3cc58b..1f309a19b14 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -111,6 +111,16 @@ export default {
required: false,
default: false,
},
+ showCommentToolBar: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ restrictedToolBarItems: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -331,7 +341,7 @@ export default {
:enable-preview="enablePreview"
:show-suggest-popover="showSuggestPopover"
:suggestion-start-index="suggestionsStartIndex"
- data-testid="markdownHeader"
+ :restricted-tool-bar-items="restrictedToolBarItems"
@preview-markdown="showPreviewTab"
@write-markdown="showWriteTab"
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
@@ -350,6 +360,7 @@ export default {
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
+ :show-comment-tool-bar="showCommentToolBar"
/>
</div>
</div>
@@ -362,8 +373,6 @@ export default {
<suggestions
v-if="hasSuggestion"
:note-html="markdownPreview"
- :from-line="lineNumber"
- :from-content="lineContent"
:line-type="lineType"
:disabled="true"
:suggestions="suggestions"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index d0bd5046bf0..ba2b5eaa4f9 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -54,6 +54,11 @@ export default {
required: false,
default: true,
},
+ restrictedToolBarItems: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -193,7 +198,10 @@ export default {
<toolbar-button
tag="**"
:button-title="
- sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.bold"
icon="bold"
@@ -201,22 +209,28 @@ export default {
<toolbar-button
tag="_"
:button-title="
- sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.italic"
icon="italic"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('strikethrough')"
tag="~~"
:button-title="
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
- modifierKey,
+ modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.strikethrough"
icon="strikethrough"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('quote')"
:prepend="true"
:tag="tag"
:button-title="__('Insert a quote')"
@@ -266,30 +280,37 @@ export default {
tag="[{text}](url)"
tag-select="url"
:button-title="
- sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
+ /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
+ sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
+ modifierKey,
+ }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.link"
icon="link"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('bullet-list')"
:prepend="true"
tag="- "
:button-title="__('Add a bullet list')"
icon="list-bulleted"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('numbered-list')"
:prepend="true"
tag="1. "
:button-title="__('Add a numbered list')"
icon="list-numbered"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('task-list')"
:prepend="true"
tag="- [ ] "
:button-title="__('Add a task list')"
icon="list-task"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('collapsible-section')"
:tag="mdCollapsibleSection"
:prepend="true"
tag-select="Click to expand"
@@ -297,12 +318,14 @@ export default {
icon="details-block"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('table')"
:tag="mdTable"
:prepend="true"
:button-title="__('Add a table')"
icon="table"
/>
<toolbar-button
+ v-if="!restrictedToolBarItems.includes('full-screen')"
class="js-zen-enter"
:prepend="true"
:button-title="__('Go full screen')"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index f1c293c87f4..6c99a749edc 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -24,6 +24,11 @@ export default {
required: false,
default: true,
},
+ showCommentToolBar: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
hasQuickActionsDocsPath() {
@@ -34,24 +39,33 @@ export default {
</script>
<template>
- <div class="comment-toolbar clearfix">
+ <div v-if="showCommentToolBar" class="comment-toolbar clearfix">
<div class="toolbar-text">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank">
- {{ __('Markdown is supported') }}
- </gl-link>
+ <gl-sprintf
+ :message="
+ s__('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}')
+ "
+ >
+ <template #markdownDocsLink="{ content }">
+ <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
</template>
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
<gl-sprintf
:message="
- __(
- '%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported',
+ s__(
+ 'NoteToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}. For %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd}, type %{keyboardStart}/%{keyboardEnd}.',
)
"
>
<template #markdownDocsLink="{ content }">
<gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link>
</template>
+ <template #keyboard="{ content }">
+ <kbd>{{ content }}</kbd>
+ </template>
<template #quickActionsDocsLink="{ content }">
<gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue
index 3e796a73f72..e23721da223 100644
--- a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue
+++ b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue
@@ -90,7 +90,9 @@ export default {
modal-id="upload-metric-modal"
size="sm"
:action-primary="actionPrimaryProps"
- :action-cancel="{ text: $options.i18n.modalCancel }"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: $options.i18n.modalCancel,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:title="$options.i18n.modalTitle"
:visible="modalVisible"
@hidden="clearInputs"
diff --git a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue
index 8eb8e52728d..bbbaaeb8a9e 100644
--- a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue
+++ b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue
@@ -159,7 +159,9 @@ export default {
size="sm"
:visible="modalVisible"
:action-primary="deleteActionPrimaryProps"
- :action-cancel="{ text: $options.i18n.modalCancel }"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: $options.i18n.modalCancel,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@primary.prevent="onDelete"
@hidden="resetEditFields"
>
@@ -177,7 +179,9 @@ export default {
modal-id="edit-metric-modal"
size="sm"
:action-primary="updateActionPrimaryProps"
- :action-cancel="{ text: $options.i18n.modalCancel }"
+ :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ text: $options.i18n.modalCancel,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:visible="editModalVisible"
data-testid="metric-image-edit-modal"
@hidden="resetEditFields"
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index a069d1cd756..21212e82de4 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -61,7 +61,9 @@ export default {
v-for="(tab, i) in tabs"
:key="i"
:title-link-class="`js-${scope}-tab-${tab.scope} gl-display-inline-flex`"
- :title-link-attributes="{ 'data-testid': `${scope}-tab-${tab.scope}` }"
+ :title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
+ 'data-testid': `${scope}-tab-${tab.scope}`,
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
:active="tab.isActive"
@click="onTabClick(tab)"
>
diff --git a/app/assets/javascripts/vue_shared/components/paginated_list.vue b/app/assets/javascripts/vue_shared/components/paginated_list.vue
index e19b8510399..ddc7a457b98 100644
--- a/app/assets/javascripts/vue_shared/components/paginated_list.vue
+++ b/app/assets/javascripts/vue_shared/components/paginated_list.vue
@@ -29,7 +29,7 @@ export default {
</template>
<template #default="{ listItem, query }">
- <slot :listItem="listItem" :query="query"></slot>
+ <slot :list-item="listItem" :query="query"></slot>
</template>
</gl-paginated-list>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue
index 6bb321713d5..a8b250f2041 100644
--- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue
@@ -32,7 +32,6 @@ export default {
return {
'gl-border-t-transparent': !this.first && !this.selected,
'gl-border-t-gray-100': this.first && !this.selected,
- 'gl-opacity-5': this.disabled,
'gl-border-b-gray-100': !this.selected,
'gl-bg-blue-50 gl-border-blue-200': this.selected,
};
diff --git a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue
index 767a108dde5..da68fe961a6 100644
--- a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue
@@ -116,6 +116,7 @@ export default {
@clear="clearSearch"
/>
<gl-sorting
+ data-testid="registry-sort-dropdown"
:text="sortText"
:is-ascending="isSortAscending"
@sortDirectionChange="onDirectionChange"
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index d5493aa5a66..9eaaf7d1c18 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -50,6 +50,11 @@ export default {
required: false,
default: null,
},
+ defaultPlatformName: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
apollo: {
platforms: {
@@ -64,9 +69,10 @@ export default {
});
},
result() {
- // Select first platform by default
- if (this.platforms?.[0]) {
- this.selectPlatform(this.platforms[0]);
+ if (this.platforms.length) {
+ // If it is set and available, select the defaultSelectedPlatform.
+ // Otherwise, select the first available platform
+ this.selectPlatform(this.defaultPlatform() || this.platforms[0]);
}
},
error() {
@@ -138,6 +144,14 @@ export default {
show() {
this.$refs.modal.show();
},
+ focusSelected() {
+ // By default the first platform always gets the focus, but when the `defaultPlatformName`
+ // property is present, any other platform might actually be selected.
+ this.$refs[this.selectedPlatformName]?.[0].$el.focus();
+ },
+ defaultPlatform() {
+ return this.platforms.find((platform) => platform.name === this.defaultPlatformName);
+ },
selectPlatform(platform) {
this.selectedPlatform = platform;
@@ -182,6 +196,8 @@ export default {
:title="$options.i18n.installARunner"
:action-secondary="$options.closeButton"
v-bind="$attrs"
+ v-on="$listeners"
+ @shown="focusSelected"
>
<gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)">
{{ $options.i18n.fetchError }}
@@ -203,6 +219,7 @@ export default {
<gl-button
v-for="platform in platforms"
:key="platform.name"
+ :ref="platform.name"
:selected="selectedPlatform && selectedPlatform.name === platform.name"
@click="selectPlatform(platform)"
>
diff --git a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
new file mode 100644
index 00000000000..f50706b6de8
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlButtonGroup, GlButton } from '@gitlab/ui';
+
+// TODO: We're planning to move this component to GitLab UI
+// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1787
+export default {
+ components: {
+ GlButtonGroup,
+ GlButton,
+ },
+ props: {
+ options: {
+ type: Array,
+ required: true,
+ },
+ value: {
+ type: [String, Number, Boolean],
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <gl-button-group>
+ <gl-button
+ v-for="opt in options"
+ :key="opt.value"
+ :disabled="!!opt.disabled"
+ :selected="value === opt.value"
+ @click="$emit('input', opt.value)"
+ >
+ <slot name="button-content" v-bind="opt">{{ opt.text }}</slot>
+ </gl-button>
+ </gl-button-group>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
index 12daaea8758..dfa2ca2d20c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue
@@ -175,7 +175,7 @@ export default {
:debounce="300"
/>
<div data-testid="content" class="dropdown-content">
- <gl-loading-icon v-if="projectsListLoading" size="md" class="gl-p-5" />
+ <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" />
<ul v-else>
<gl-dropdown-item
v-for="project in projects"
@@ -199,7 +199,7 @@ export default {
>
<gl-button
category="primary"
- variant="success"
+ variant="confirm"
:disabled="!Boolean(selectedProject)"
class="gl-text-center! issuable-move-button"
@click="handleMoveClick"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
index 3ec33a653b8..2cccb8325f4 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue
@@ -104,7 +104,7 @@ export default {
<gl-button
:disabled="disableCreate"
category="primary"
- variant="success"
+ variant="confirm"
class="float-left d-flex align-items-center"
@click="handleCreateClick"
>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
index 623e7799493..134575b7a27 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
@@ -185,7 +185,7 @@ export default {
<gl-loading-icon
v-if="labelsFetchInProgress"
class="labels-fetch-loading gl-align-items-center w-100 h-100"
- size="md"
+ size="lg"
/>
<ul v-else class="list-unstyled gl-mb-0 gl-word-break-word">
<label-item
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
index 7989ad40b5a..e91a0489ef1 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue
@@ -23,7 +23,7 @@ export default {
</script>
<template>
- <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
+ <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold">
{{ __('Labels') }}
<template v-if="allowLabelEdit">
<gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline />
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
index 88977652556..090bf9493bf 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
@@ -179,7 +179,7 @@ export default {
<gl-button
:disabled="disableCreate"
category="primary"
- variant="success"
+ variant="confirm"
class="gl-display-flex gl-align-items-center"
data-testid="create-button"
@click="createLabel"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
index ae179ef93c7..f595e635f2c 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
@@ -147,7 +147,7 @@ export default {
<gl-loading-icon
v-if="labelsFetchInProgress"
class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3"
- size="md"
+ size="lg"
/>
<template v-else>
<gl-dropdown-item
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
index 1b8e4bcfec6..c30ca5369ee 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
@@ -36,6 +36,9 @@ export default {
return content;
},
+ firstLineClass() {
+ return { 'gl-mt-3!': this.number === 1 };
+ },
},
methods: {
wrapBidiChar(bidiChar) {
@@ -56,10 +59,11 @@ export default {
</script>
<template>
<div class="gl-display-flex">
- <div class="line-numbers gl-pt-0! gl-pb-0! gl-absolute gl-z-index-3">
+ <div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers">
<gl-link
:id="`L${number}`"
- class="file-line-num diff-line-num gl-user-select-none"
+ class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num"
+ :class="firstLineClass"
:to="`#L${number}`"
:data-line-number="number"
>
@@ -68,7 +72,8 @@ export default {
</div>
<pre
- class="code highlight gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11!"
+ class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight"
+ :class="firstLineClass"
><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index edf2229a9a1..ed87a202b15 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -2,6 +2,7 @@
import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
import LineHighlighter from '~/blob/line_highlighter';
import eventHub from '~/notes/event_hub';
+import languageLoader from '~/content_editor/services/highlight_js_language_loader';
import { ROUGE_TO_HLJS_LANGUAGE_MAP, LINES_PER_CHUNK } from './constants';
import Chunk from './components/chunk.vue';
@@ -129,7 +130,7 @@ export default {
let languageDefinition;
try {
- languageDefinition = await import(`highlight.js/lib/languages/${this.language}`);
+ languageDefinition = await languageLoader[this.language]();
this.hljs.registerLanguage(this.language, languageDefinition.default);
} catch (message) {
this.$emit('error', message);
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index e784bba6698..0d466df1b7f 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -42,6 +42,6 @@ export default {
:class="cssClass"
:title="tooltipTitle(time)"
:datetime="time"
- ><slot :timeAgo="timeAgo">{{ timeAgo }}</slot></time
+ ><slot :time-ago="timeAgo">{{ timeAgo }}</slot></time
>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
index 62de76e46b5..f62bf686f85 100644
--- a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
+++ b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
@@ -160,7 +160,7 @@ export default {
>
<gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" />
<p class="gl-mb-0" data-testid="upload-text">
- <slot name="upload-text" :openFileUpload="openFileUpload">
+ <slot name="upload-text" :open-file-upload="openFileUpload">
<gl-sprintf
:message="
singleFileSelection
diff --git a/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue b/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue
new file mode 100644
index 00000000000..bc5e0cf10dd
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlSkeletonLoader } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'UsageBanner',
+ components: {
+ GlSkeletonLoader,
+ },
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ i18n: {
+ dependencyProxy: s__('UsageQuota|Dependency proxy'),
+ storageUsed: s__('UsageQuota|Storage used'),
+ dependencyProxyMessage: s__(
+ 'UsageQuota|Local proxy used for frequently-accessed upstream Docker images. %{linkStart}More information%{linkEnd}',
+ ),
+ },
+ storageUsageQuotaHelpPage: helpPagePath('user/usage_quotas'),
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-flex-direction-column">
+ <div class="gl-display-flex gl-align-items-center gl-py-3">
+ <div
+ class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1"
+ >
+ <div class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1">
+ <div
+ v-if="$slots['left-primary-text']"
+ class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0 gl-mb-4"
+ >
+ <slot name="left-primary-text"></slot>
+ </div>
+ <div
+ v-if="$slots['left-secondary-text']"
+ class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-grow-1 gl-w-70p gl-md-max-w-70p"
+ >
+ <slot name="left-secondary-text"></slot>
+ </div>
+ </div>
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500 gl-flex-shrink-0"
+ >
+ <div
+ v-if="$slots['right-primary-text']"
+ class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
+ >
+ <slot name="right-primary-text"></slot>
+ </div>
+ <div
+ v-if="$slots['right-secondary-text']"
+ class="gl-display-flex gl-align-items-center gl-min-h-6"
+ >
+ <slot v-if="!loading" name="right-secondary-text"></slot>
+ <gl-skeleton-loader v-else :width="60" :lines="1" />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index cac8f0a9aa5..ec7a7cd72ae 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -6,9 +6,13 @@ import {
GlIcon,
GlSafeHtmlDirective,
GlSprintf,
+ GlButton,
} from '@gitlab/ui';
+import { __ } from '~/locale';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
import { glEmojiTag } from '~/emoji';
+import createFlash from '~/flash';
+import { followUser, unfollowUser } from '~/rest_api';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
const MAX_SKELETON_LINES = 4;
@@ -24,6 +28,7 @@ export default {
UserAvatarImage,
UserNameWithStatus,
GlSprintf,
+ GlButton,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
@@ -38,6 +43,16 @@ export default {
required: true,
default: null,
},
+ placement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ },
+ data() {
+ return {
+ toggleFollowLoading: false,
+ };
},
computed: {
statusHtml() {
@@ -59,6 +74,59 @@ export default {
availabilityStatus() {
return this.user?.status?.availability || '';
},
+ isNotCurrentUser() {
+ return !this.userIsLoading && this.user.username !== gon.current_username;
+ },
+ shouldRenderToggleFollowButton() {
+ return this.isNotCurrentUser && typeof this.user?.isFollowed !== 'undefined';
+ },
+ toggleFollowButtonText() {
+ if (this.toggleFollowLoading) return null;
+
+ return this.user?.isFollowed ? __('Unfollow') : __('Follow');
+ },
+ toggleFollowButtonVariant() {
+ return this.user?.isFollowed ? 'default' : 'confirm';
+ },
+ },
+ methods: {
+ async toggleFollow() {
+ if (this.user.isFollowed) {
+ this.unfollow();
+ } else {
+ this.follow();
+ }
+ },
+ async follow() {
+ this.toggleFollowLoading = true;
+ try {
+ await followUser(this.user.id);
+ this.$emit('follow');
+ } catch (error) {
+ createFlash({
+ message: __('An error occurred while trying to follow this user, please try again.'),
+ error,
+ captureError: true,
+ });
+ } finally {
+ this.toggleFollowLoading = false;
+ }
+ },
+ async unfollow() {
+ this.toggleFollowLoading = true;
+ try {
+ await unfollowUser(this.user.id);
+ this.$emit('unfollow');
+ } catch (error) {
+ createFlash({
+ message: __('An error occurred while trying to unfollow this user, please try again.'),
+ error,
+ captureError: true,
+ });
+ } finally {
+ this.toggleFollowLoading = false;
+ }
+ },
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
@@ -66,12 +134,24 @@ export default {
<template>
<!-- 200ms delay so not every mouseover triggers Popover -->
- <gl-popover :target="target" :delay="200" boundary="viewport" placement="top">
+ <gl-popover :target="target" :delay="200" :placement="placement" boundary="viewport">
<div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover">
- <div class="gl-p-2 flex-shrink-1">
- <user-avatar-image :img-src="user.avatarUrl" :size="64" css-classes="gl-mr-3!" />
+ <div
+ class="gl-p-2 flex-shrink-1 gl-display-flex gl-flex-direction-column align-items-center gl-w-70p"
+ >
+ <user-avatar-image :img-src="user.avatarUrl" :size="64" css-classes="gl-m-0!" />
+ <div v-if="shouldRenderToggleFollowButton" class="gl-mt-3">
+ <gl-button
+ :variant="toggleFollowButtonVariant"
+ :loading="toggleFollowLoading"
+ size="small"
+ data-testid="toggle-follow-button"
+ @click="toggleFollow"
+ >{{ toggleFollowButtonText }}</gl-button
+ >
+ </div>
</div>
- <div class="gl-p-2 gl-w-full gl-min-w-0">
+ <div class="gl-w-full gl-min-w-0 gl-word-break-word">
<template v-if="userIsLoading">
<gl-skeleton-loader
:lines="$options.maxSkeletonLines"
@@ -94,7 +174,7 @@ export default {
<div class="gl-text-gray-500">
<div v-if="user.bio" class="gl-display-flex gl-mb-2">
<gl-icon name="profile" class="gl-flex-shrink-0" />
- <span ref="bio" class="gl-ml-2 gl-overflow-hidden">{{ user.bio }}</span>
+ <span ref="bio" class="gl-ml-2">{{ user.bio }}</span>
</div>
<div v-if="user.workInformation" class="gl-display-flex gl-mb-2">
<gl-icon name="work" class="gl-flex-shrink-0" />
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index 9df5254155e..91f20863089 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -298,7 +298,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
data-testid="loading-participants"
- size="md"
+ size="lg"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>
diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js
index 9cb66f6e65f..3ebeec4a50b 100644
--- a/app/assets/javascripts/vue_shared/constants.js
+++ b/app/assets/javascripts/vue_shared/constants.js
@@ -1,4 +1,5 @@
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
+import { IssuableType, WorkspaceType } from '~/issues/constants';
const INTERVALS = {
minute: 'minute',
@@ -66,3 +67,14 @@ export const getTimeWindow = (timeWindowName) =>
export const AVATAR_SHAPE_OPTION_CIRCLE = 'circle';
export const AVATAR_SHAPE_OPTION_RECT = 'rect';
+
+export const confidentialityInfoText = (workspaceType, issuableType) =>
+ sprintf(
+ __(
+ 'Only %{workspaceType} members with at least Reporter role can view or be notified about this %{issuableType}.',
+ ),
+ {
+ workspaceType: workspaceType === WorkspaceType.project ? __('project') : __('group'),
+ issuableType: issuableType === IssuableType.Issue ? __('issue') : __('epic'),
+ },
+ );
diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue
index c216a05bdb0..0758cb507e9 100644
--- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue
+++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue
@@ -1,5 +1,5 @@
<script>
-import { GlForm, GlFormInput } from '@gitlab/ui';
+import { GlForm, GlFormInput, GlFormGroup } from '@gitlab/ui';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
@@ -9,6 +9,7 @@ export default {
components: {
GlForm,
GlFormInput,
+ GlFormGroup,
MarkdownField,
LabelsSelect,
},
@@ -37,6 +38,7 @@ export default {
selectedLabels: [],
};
},
+ computed: {},
methods: {
handleUpdateSelectedLabels(labels) {
if (labels.length) {
@@ -52,12 +54,15 @@ export default {
<div data-testid="issuable-title" class="form-group row">
<label for="issuable-title" class="col-form-label col-sm-2">{{ __('Title') }}</label>
<div class="col-sm-10">
- <gl-form-input
- id="issuable-title"
- v-model="issuableTitle"
- :autofocus="true"
- :placeholder="__('Title')"
- />
+ <gl-form-group :description="__('Maximum of 255 characters')">
+ <gl-form-input
+ id="issuable-title"
+ v-model="issuableTitle"
+ maxlength="255"
+ :autofocus="true"
+ :placeholder="__('Title')"
+ />
+ </gl-form-group>
</div>
</div>
<div data-testid="issuable-description" class="form-group row">
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
index 8008b85bbdb..6453290f6ea 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
@@ -55,7 +55,7 @@ export default {
return createdSecondsAgo < SECONDS_IN_DAY;
},
author() {
- return this.issuable.author;
+ return this.issuable.author || {};
},
webUrl() {
return this.issuable.gitlabWebUrl || this.issuable.webUrl;
@@ -215,7 +215,7 @@ export default {
<span class="gl-display-none gl-sm-display-inline">
<span aria-hidden="true">&middot;</span>
<span class="issuable-authored gl-mr-3">
- <gl-sprintf :message="__('created %{timeAgo} by %{author}')">
+ <gl-sprintf v-if="author.name" :message="__('created %{timeAgo} by %{author}')">
<template #timeAgo>
<span
v-gl-tooltip.bottom
@@ -241,6 +241,17 @@ export default {
</gl-link>
</template>
</gl-sprintf>
+ <gl-sprintf v-else :message="__('created %{timeAgo}')">
+ <template #timeAgo>
+ <span
+ v-gl-tooltip.bottom
+ :title="tooltipTitle(issuable.createdAt)"
+ data-testid="issuable-created-at"
+ >
+ {{ createdAt }}
+ </span>
+ </template>
+ </gl-sprintf>
</span>
<slot name="timeframe"></slot>
</span>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
index 20f178dfb7d..8b293b2e9f6 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
@@ -168,6 +168,11 @@ export default {
required: false,
default: '',
},
+ syncFilterAndSort: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -282,6 +287,7 @@ export default {
:sort-options="sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
+ :sync-filter-and-sort="syncFilterAndSort"
:show-checkbox="showBulkEditSidebar"
:checkbox-checked="allIssuablesChecked"
class="gl-flex-grow-1 gl-border-t-none row-content-block"
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
index ee7e113af72..649dbd6576b 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
@@ -1,14 +1,23 @@
<script>
-import { GlIcon, GlButton, GlTooltipDirective, GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui';
+import {
+ GlIcon,
+ GlBadge,
+ GlButton,
+ GlTooltipDirective,
+ GlAvatarLink,
+ GlAvatarLabeled,
+} from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isExternal } from '~/lib/utils/url_utility';
import { n__, sprintf } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { IssuableStates } from '~/vue_shared/issuable/list/constants';
export default {
components: {
GlIcon,
+ GlBadge,
GlButton,
GlAvatarLink,
GlAvatarLabeled,
@@ -26,6 +35,11 @@ export default {
type: Object,
required: true,
},
+ issuableState: {
+ type: String,
+ required: false,
+ default: '',
+ },
statusBadgeClass: {
type: String,
required: false,
@@ -36,6 +50,11 @@ export default {
required: false,
default: '',
},
+ statusIconClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
blocked: {
type: Boolean,
required: false,
@@ -53,6 +72,9 @@ export default {
},
},
computed: {
+ badgeVariant() {
+ return this.issuableState === IssuableStates.Opened ? 'success' : 'info';
+ },
authorId() {
return getIdFromGraphQLId(`${this.author.id}`);
},
@@ -71,6 +93,9 @@ export default {
{ completedCount, count },
);
},
+ hasTasks() {
+ return this.taskCompletionStatus.count > 0;
+ },
},
mounted() {
this.toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button');
@@ -88,10 +113,15 @@ export default {
<template>
<div class="detail-page-header">
<div class="detail-page-header-body">
- <div data-testid="status" class="issuable-status-box status-box" :class="statusBadgeClass">
- <gl-icon v-if="statusIcon" :name="statusIcon" class="d-block d-sm-none" />
- <span class="d-none d-sm-block"><slot name="status-badge"></slot></span>
- </div>
+ <gl-badge
+ data-testid="status"
+ class="issuable-status-badge gl-mr-3"
+ :class="statusBadgeClass"
+ :variant="badgeVariant"
+ >
+ <gl-icon v-if="statusIcon" :name="statusIcon" :class="statusIconClass" />
+ <span class="gl-display-none gl-sm-display-block"><slot name="status-badge"></slot></span>
+ </gl-badge>
<div class="issuable-meta gl-display-flex gl-align-items-center d-md-inline-block">
<div v-if="blocked || confidential" class="gl-display-inline-block">
<div v-if="blocked" data-testid="blocked" class="issuable-warning-icon inline">
@@ -128,7 +158,7 @@ export default {
<strong class="author d-sm-none d-inline">@{{ author.username }}</strong>
</gl-avatar-link>
<span
- v-if="taskCompletionStatus"
+ v-if="taskCompletionStatus && hasTasks"
data-testid="task-status"
class="gl-display-none gl-md-display-block gl-lg-display-inline-block"
>{{ taskStatusString }}</span
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
index 8849af2a52e..c165ee91c59 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
@@ -27,6 +27,11 @@ export default {
required: false,
default: '',
},
+ statusIconClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
enableEdit: {
type: Boolean,
required: false,
@@ -102,8 +107,10 @@ export default {
<template>
<div class="issuable-show-container" data-qa-selector="issuable_show_container">
<issuable-header
+ :issuable-state="issuable.state"
:status-badge-class="statusBadgeClass"
:status-icon="statusIcon"
+ :status-icon-class="statusIconClass"
:blocked="issuable.blocked"
:confidential="issuable.confidential"
:created-at="issuable.createdAt"
@@ -122,6 +129,7 @@ export default {
:issuable="issuable"
:status-badge-class="statusBadgeClass"
:status-icon="statusIcon"
+ :status-icon-class="statusIconClass"
:enable-edit="enableEdit"
:enable-autocomplete="enableAutocomplete"
:enable-autosave="enableAutosave"
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
index 45941174a62..47f05a2cee2 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
@@ -86,7 +86,7 @@ export default {
>
<p
data-testid="status"
- class="issuable-status-box status-box gl-my-0"
+ class="issuable-status-box status-box gl-white-space-nowrap gl-my-0"
:class="statusBadgeClass"
>
<gl-icon :name="statusIcon" class="gl-display-block d-sm-none gl-h-6!" />
diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js
index c5f41d81167..2a0256548a8 100644
--- a/app/assets/javascripts/vue_shared/mixins/timeago.js
+++ b/app/assets/javascripts/vue_shared/mixins/timeago.js
@@ -1,4 +1,4 @@
-import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
+import { formatDate, getTimeago, timeagoLanguageCode } from '~/lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
@@ -8,7 +8,7 @@ export default {
timeFormatted(time) {
const timeago = getTimeago();
- return timeago.format(time);
+ return timeago.format(time, timeagoLanguageCode);
},
tooltipTitle(time) {
diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue b/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue
new file mode 100644
index 00000000000..6045d75ac11
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue
@@ -0,0 +1,34 @@
+<script>
+import SectionLoader from './section_loader.vue';
+
+export default {
+ name: 'SectionLayout',
+ components: {
+ SectionLoader,
+ },
+ props: {
+ heading: {
+ type: String,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row gl-m-0 gl-border-b gl-line-height-20 gl-py-6">
+ <div class="col-lg-4 gl-pl-0 gl-pr-9">
+ <h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2>
+ <slot name="description"></slot>
+ </div>
+ <div class="col-lg-8 gl-pr-0 gl-pl-0">
+ <section-loader v-if="isLoading" />
+ <slot v-else name="features"></slot>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue b/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue
new file mode 100644
index 00000000000..b15e25b0943
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlCard, GlSkeletonLoader } from '@gitlab/ui';
+
+export default {
+ name: 'SectionLoader',
+ components: {
+ GlCard,
+ GlSkeletonLoader,
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-skeleton-loader :width="1248" :height="180">
+ <rect x="0" y="0" width="100" height="15" rx="4" />
+ <rect x="0" y="24" width="460" height="32" rx="4" />
+ <rect x="0" y="71" width="100" height="15" rx="4" />
+ <rect x="0" y="95" width="460" height="72" rx="4" />
+ </gl-skeleton-loader>
+ <gl-card v-for="i in 2" :key="i" class="gl-mb-5">
+ <template #header>
+ <gl-skeleton-loader :width="1248" :height="15">
+ <rect x="0" y="0" width="300" height="15" rx="4" />
+ </gl-skeleton-loader>
+ </template>
+ <gl-skeleton-loader :width="1248" :height="15">
+ <rect x="0" y="0" width="600" height="15" rx="4" />
+ </gl-skeleton-loader>
+ <gl-skeleton-loader :width="1248" :height="15">
+ <rect x="0" y="0" width="300" height="15" rx="4" />
+ </gl-skeleton-loader>
+ </gl-card>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js
index 458bacce915..6a4f671abb9 100644
--- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js
+++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js
@@ -90,7 +90,7 @@ const createStatusMessage = ({ reportType, status, total }) => {
if (status) {
message = __('%{reportType} %{status}');
} else if (!total) {
- message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.');
+ message = __('%{reportType} detected no %{totalStart}new%{totalEnd} vulnerabilities.');
} else {
message = __(
'%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}',
diff --git a/app/assets/javascripts/whats_new/components/feature.vue b/app/assets/javascripts/whats_new/components/feature.vue
index a93bda326de..90f6230ef72 100644
--- a/app/assets/javascripts/whats_new/components/feature.vue
+++ b/app/assets/javascripts/whats_new/components/feature.vue
@@ -37,9 +37,11 @@ export default {
<template>
<div class="gl-py-6 gl-px-6 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
<gl-link
+ v-if="feature.image_url"
:href="feature.url"
target="_blank"
class="gl-display-block"
+ data-testid="whats-new-image-link"
data-track-action="click_whats_new_item"
:data-track-label="feature.title"
:data-track-property="feature.url"
@@ -67,9 +69,11 @@ export default {
v-for="packageName in feature.packages"
:key="packageName"
size="md"
- class="whats-new-item-badge gl-mr-2"
+ variant="tier"
+ icon="license"
+ class="gl-mr-2"
>
- <gl-icon name="license" />{{ packageName }}
+ {{ packageName }}
</gl-badge>
</div>
<div
diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue
new file mode 100644
index 00000000000..0b6c1a75bb2
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/item_state.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { STATE_OPEN, STATE_CLOSED } from '../constants';
+
+export default {
+ i18n: {
+ status: __('Status'),
+ },
+ states: [
+ {
+ value: STATE_OPEN,
+ text: __('Open'),
+ },
+ {
+ value: STATE_CLOSED,
+ text: __('Closed'),
+ },
+ ],
+ components: {
+ GlFormGroup,
+ GlFormSelect,
+ },
+ props: {
+ state: {
+ type: String,
+ required: true,
+ },
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ currentState() {
+ return this.$options.states[this.state];
+ },
+ },
+ methods: {
+ setState(newState) {
+ if (newState !== this.state) {
+ this.$emit('changed', newState);
+ }
+ },
+ },
+ labelId: 'work-item-state-select',
+};
+</script>
+
+<template>
+ <gl-form-group :label="$options.i18n.status" :label-for="$options.labelId">
+ <gl-form-select
+ :id="$options.labelId"
+ :value="state"
+ :options="$options.states"
+ :disabled="loading"
+ class="gl-w-auto"
+ @change="setState"
+ />
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_actions.vue b/app/assets/javascripts/work_items/components/work_item_actions.vue
index 40b6fcdd204..31e4a932c5a 100644
--- a/app/assets/javascripts/work_items/components/work_item_actions.vue
+++ b/app/assets/javascripts/work_items/components/work_item_actions.vue
@@ -1,7 +1,7 @@
<script>
import { GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
-import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql';
+import Tracking from '~/tracking';
export default {
i18n: {
@@ -15,55 +15,36 @@ export default {
directives: {
GlModal: GlModalDirective,
},
+ mixins: [Tracking.mixin({ label: 'actions_menu' })],
props: {
workItemId: {
type: String,
required: false,
default: null,
},
- canUpdate: {
+ canDelete: {
type: Boolean,
required: false,
default: false,
},
},
- emits: ['workItemDeleted', 'error'],
+ emits: ['deleteWorkItem'],
methods: {
- deleteWorkItem() {
- this.$apollo
- .mutate({
- mutation: deleteWorkItemMutation,
- variables: {
- input: {
- id: this.workItemId,
- },
- },
- })
- .then(({ data: { workItemDelete, errors } }) => {
- if (errors?.length) {
- throw new Error(errors[0].message);
- }
-
- if (workItemDelete?.errors.length) {
- throw new Error(workItemDelete.errors[0]);
- }
-
- this.$emit('workItemDeleted');
- })
- .catch((e) => {
- this.$emit(
- 'error',
- e.message ||
- s__('WorkItem|Something went wrong when deleting the work item. Please try again.'),
- );
- });
+ handleDeleteWorkItem() {
+ this.track('click_delete_work_item');
+ this.$emit('deleteWorkItem');
+ },
+ handleCancelDeleteWorkItem({ trigger }) {
+ if (trigger !== 'ok') {
+ this.track('cancel_delete_work_item');
+ }
},
},
};
</script>
<template>
- <div v-if="canUpdate">
+ <div v-if="canDelete">
<gl-dropdown
icon="ellipsis_v"
text-sr-only
@@ -81,7 +62,8 @@ export default {
:title="$options.i18n.deleteWorkItem"
:ok-title="$options.i18n.deleteWorkItem"
ok-variant="danger"
- @ok="deleteWorkItem"
+ @ok="handleDeleteWorkItem"
+ @hide="handleCancelDeleteWorkItem"
>
{{
s__(
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index f2fb1e3ccbc..4222ffe42fe 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -1,15 +1,20 @@
<script>
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
import { i18n } from '../constants';
import workItemQuery from '../graphql/work_item.query.graphql';
import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql';
+import WorkItemActions from './work_item_actions.vue';
+import WorkItemState from './work_item_state.vue';
import WorkItemTitle from './work_item_title.vue';
export default {
i18n,
components: {
GlAlert,
+ GlSkeletonLoader,
+ WorkItemActions,
WorkItemTitle,
+ WorkItemState,
},
props: {
workItemId: {
@@ -49,9 +54,18 @@ export default {
},
},
computed: {
+ workItemLoading() {
+ return this.$apollo.queries.workItem.loading;
+ },
workItemType() {
return this.workItem.workItemType?.name;
},
+ canUpdate() {
+ return this.workItem?.userPermissions?.updateWorkItem;
+ },
+ canDelete() {
+ return this.workItem?.userPermissions?.deleteWorkItem;
+ },
},
};
</script>
@@ -62,12 +76,35 @@ export default {
{{ error }}
</gl-alert>
- <work-item-title
- :loading="$apollo.queries.workItem.loading"
- :work-item-id="workItem.id"
- :work-item-title="workItem.title"
- :work-item-type="workItemType"
- @error="error = $event"
- />
+ <div v-if="workItemLoading" class="gl-max-w-26 gl-py-5">
+ <gl-skeleton-loader :height="65" :width="240">
+ <rect width="240" height="20" x="5" y="0" rx="4" />
+ <rect width="100" height="20" x="5" y="45" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ <template v-else>
+ <div class="gl-display-flex">
+ <work-item-title
+ :work-item-id="workItem.id"
+ :work-item-title="workItem.title"
+ :work-item-type="workItemType"
+ class="gl-mr-5"
+ @error="error = $event"
+ @updated="$emit('workItemUpdated')"
+ />
+ <work-item-actions
+ :work-item-id="workItem.id"
+ :can-delete="canDelete"
+ class="gl-ml-auto gl-mt-5"
+ @deleteWorkItem="$emit('deleteWorkItem')"
+ @error="error = $event"
+ />
+ </div>
+ <work-item-state
+ :work-item="workItem"
+ @error="error = $event"
+ @updated="$emit('workItemUpdated')"
+ />
+ </template>
</section>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
index a79091fb8b2..172a40a6e56 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
@@ -1,42 +1,87 @@
<script>
-import { GlAlert, GlButton, GlModal } from '@gitlab/ui';
-import WorkItemActions from './work_item_actions.vue';
+import { GlAlert, GlModal } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import deleteWorkItemFromTaskMutation from '../graphql/delete_task_from_work_item.mutation.graphql';
import WorkItemDetail from './work_item_detail.vue';
export default {
components: {
GlAlert,
- GlButton,
GlModal,
WorkItemDetail,
- WorkItemActions,
},
props: {
- canUpdate: {
- type: Boolean,
+ workItemId: {
+ type: String,
required: false,
- default: false,
+ default: null,
},
- visible: {
- type: Boolean,
- required: true,
+ issueGid: {
+ type: String,
+ required: false,
+ default: '',
},
- workItemId: {
+ lockVersion: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ lineNumberStart: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ lineNumberEnd: {
type: String,
required: false,
default: null,
},
},
- emits: ['workItemDeleted', 'close'],
+ emits: ['workItemDeleted', 'workItemUpdated', 'close'],
data() {
return {
error: undefined,
};
},
methods: {
- handleWorkItemDeleted() {
- this.$emit('workItemDeleted');
- this.closeModal();
+ deleteWorkItem() {
+ this.$apollo
+ .mutate({
+ mutation: deleteWorkItemFromTaskMutation,
+ variables: {
+ input: {
+ id: this.issueGid,
+ lockVersion: this.lockVersion,
+ taskData: {
+ id: this.workItemId,
+ lineNumberStart: Number(this.lineNumberStart),
+ lineNumberEnd: Number(this.lineNumberEnd),
+ },
+ },
+ },
+ })
+ .then(
+ ({
+ data: {
+ workItemDeleteTask: {
+ workItem: { descriptionHtml },
+ errors,
+ },
+ },
+ }) => {
+ if (errors?.length) {
+ throw new Error(errors[0].message);
+ }
+
+ this.$emit('workItemDeleted', descriptionHtml);
+ this.$refs.modal.hide();
+ },
+ )
+ .catch((e) => {
+ this.error =
+ e.message ||
+ s__('WorkItem|Something went wrong when deleting the work item. Please try again.');
+ });
},
closeModal() {
this.error = '';
@@ -45,37 +90,31 @@ export default {
setErrorMessage(message) {
this.error = message;
},
+ show() {
+ this.$refs.modal.show();
+ },
},
};
</script>
<template>
- <gl-modal hide-footer modal-id="work-item-detail-modal" :visible="visible" @hide="closeModal">
- <template #modal-header>
- <div class="gl-w-full gl-display-flex gl-align-items-center gl-justify-content-end">
- <h2 class="modal-title gl-mr-auto">{{ s__('WorkItem|Work Item') }}</h2>
- <work-item-actions
- :work-item-id="workItemId"
- :can-update="canUpdate"
- @workItemDeleted="handleWorkItemDeleted"
- @error="setErrorMessage"
- />
- <gl-button category="tertiary" icon="close" :aria-label="__('Close')" @click="closeModal" />
- </div>
- </template>
+ <gl-modal ref="modal" hide-footer size="lg" modal-id="work-item-detail-modal" @hide="closeModal">
<gl-alert v-if="error" variant="danger" @dismiss="error = false">
{{ error }}
</gl-alert>
- <work-item-detail :work-item-id="workItemId" />
+ <work-item-detail
+ :work-item-id="workItemId"
+ @deleteWorkItem="deleteWorkItem"
+ @workItemUpdated="$emit('workItemUpdated')"
+ />
</gl-modal>
</template>
<style>
-/* hide the existing close button until we can do it
- * with https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2710
+/* hide the existing modal header
*/
-#work-item-detail-modal .modal-header > .gl-button {
+#work-item-detail-modal .modal-header {
display: none;
}
</style>
diff --git a/app/assets/javascripts/work_items/components/work_item_state.vue b/app/assets/javascripts/work_items/components/work_item_state.vue
new file mode 100644
index 00000000000..51db4c804eb
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_state.vue
@@ -0,0 +1,98 @@
+<script>
+import * as Sentry from '@sentry/browser';
+import Tracking from '~/tracking';
+import {
+ i18n,
+ STATE_OPEN,
+ STATE_CLOSED,
+ STATE_EVENT_CLOSE,
+ STATE_EVENT_REOPEN,
+} from '../constants';
+import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
+import ItemState from './item_state.vue';
+
+export default {
+ components: {
+ ItemState,
+ },
+ mixins: [Tracking.mixin()],
+ props: {
+ workItem: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ updateInProgress: false,
+ };
+ },
+ computed: {
+ workItemType() {
+ return this.workItem.workItemType?.name;
+ },
+ tracking() {
+ return {
+ category: 'workItems:show',
+ label: 'item_state',
+ property: `type_${this.workItemType}`,
+ };
+ },
+ },
+ methods: {
+ async updateWorkItemState(newState) {
+ const stateEventMap = {
+ [STATE_OPEN]: STATE_EVENT_REOPEN,
+ [STATE_CLOSED]: STATE_EVENT_CLOSE,
+ };
+
+ const stateEvent = stateEventMap[newState];
+
+ await this.updateWorkItem(stateEvent);
+ },
+ async updateWorkItem(updatedState) {
+ if (!updatedState) {
+ return;
+ }
+
+ this.updateInProgress = true;
+
+ try {
+ this.track('updated_state');
+
+ const {
+ data: { workItemUpdate },
+ } = await this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItem.id,
+ stateEvent: updatedState,
+ },
+ },
+ });
+
+ if (workItemUpdate?.errors?.length) {
+ throw new Error(workItemUpdate.errors[0]);
+ }
+
+ this.$emit('updated');
+ } catch (error) {
+ this.$emit('error', i18n.updateError);
+ Sentry.captureException(error);
+ }
+
+ this.updateInProgress = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <item-state
+ v-if="workItem.state"
+ :state="workItem.state"
+ :loading="updateInProgress"
+ @changed="updateWorkItemState"
+ />
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_title.vue b/app/assets/javascripts/work_items/components/work_item_title.vue
index 88a825853cc..d2e6d3c0bbf 100644
--- a/app/assets/javascripts/work_items/components/work_item_title.vue
+++ b/app/assets/javascripts/work_items/components/work_item_title.vue
@@ -1,5 +1,4 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
import Tracking from '~/tracking';
import { i18n } from '../constants';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
@@ -7,16 +6,10 @@ import ItemTitle from './item_title.vue';
export default {
components: {
- GlLoadingIcon,
ItemTitle,
},
mixins: [Tracking.mixin()],
props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
workItemId: {
type: String,
required: false,
@@ -59,6 +52,7 @@ export default {
},
});
this.track('updated_title');
+ this.$emit('updated');
} catch {
this.$emit('error', i18n.updateError);
}
@@ -68,6 +62,5 @@ export default {
</script>
<template>
- <gl-loading-icon v-if="loading" class="gl-mt-3" size="md" />
- <item-title v-else :title="workItemTitle" @title-changed="updateTitle" />
+ <item-title :title="workItemTitle" @title-changed="updateTitle" />
</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index d3bcaf0f95f..e914500108f 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -1,6 +1,14 @@
import { s__ } from '~/locale';
+export const STATE_OPEN = 'OPEN';
+export const STATE_CLOSED = 'CLOSED';
+
+export const STATE_EVENT_REOPEN = 'REOPEN';
+export const STATE_EVENT_CLOSE = 'CLOSE';
+
export const i18n = {
fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
};
+
+export const DEFAULT_MODAL_TYPE = 'Task';
diff --git a/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql
new file mode 100644
index 00000000000..32c07ed48c7
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql
@@ -0,0 +1,9 @@
+mutation workItemDeleteTask($input: WorkItemDeleteTaskInput!) {
+ workItemDeleteTask(input: $input) {
+ workItem {
+ id
+ descriptionHtml
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
index 2707d6bb790..e25fd102699 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
@@ -1,8 +1,14 @@
fragment WorkItem on WorkItem {
id
title
+ state
+ description
workItemType {
id
name
}
+ userPermissions {
+ deleteWorkItem
+ updateWorkItem
+ }
}
diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
index 1d3dae0649d..3b46fed97ec 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
@@ -1,6 +1,6 @@
#import "./work_item.fragment.graphql"
-query workItem($id: ID!) {
+query workItem($id: WorkItemID!) {
workItem(id: $id) {
...WorkItem
}
diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js
index 10fae9b9cc0..e39b0d6a353 100644
--- a/app/assets/javascripts/work_items/index.js
+++ b/app/assets/javascripts/work_items/index.js
@@ -5,7 +5,7 @@ import { createApolloProvider } from './graphql/provider';
export const initWorkItemsRoot = () => {
const el = document.querySelector('#js-work-items');
- const { fullPath } = el.dataset;
+ const { fullPath, issuesListPath } = el.dataset;
return new Vue({
el,
@@ -13,6 +13,7 @@ export const initWorkItemsRoot = () => {
apolloProvider: createApolloProvider(),
provide: {
fullPath,
+ issuesListPath,
},
render(createElement) {
return createElement(App);
diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue
index a95da80ac95..04c6a61689c 100644
--- a/app/assets/javascripts/work_items/pages/create_work_item.vue
+++ b/app/assets/javascripts/work_items/pages/create_work_item.vue
@@ -6,6 +6,7 @@ import workItemQuery from '../graphql/work_item.query.graphql';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
import createWorkItemFromTaskMutation from '../graphql/create_work_item_from_task.mutation.graphql';
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
+import { DEFAULT_MODAL_TYPE } from '../constants';
import ItemTitle from '../components/item_title.vue';
@@ -77,6 +78,13 @@ export default {
text: node.name,
}));
},
+ result() {
+ if (!this.selectedWorkItemType && this.isModal) {
+ this.selectedWorkItemType = this.formOptions.find(
+ (options) => options.text === DEFAULT_MODAL_TYPE,
+ )?.value;
+ }
+ },
error() {
this.error = this.$options.fetchTypesErrorText;
},
@@ -115,20 +123,15 @@ export default {
},
},
update(store, { data: { workItemCreate } }) {
- const { id, title, workItemType } = workItemCreate.workItem;
+ const { workItem } = workItemCreate;
store.writeQuery({
query: workItemQuery,
variables: {
- id,
+ id: workItem.id,
},
data: {
- workItem: {
- __typename: 'WorkItem',
- id,
- title,
- workItemType,
- },
+ workItem,
},
});
},
@@ -185,11 +188,11 @@ export default {
<form @submit.prevent="createWorkItem">
<gl-alert v-if="error" variant="danger" @dismiss="error = null">{{ error }}</gl-alert>
<div :class="{ 'gl-px-5': isModal }" data-testid="content">
- <item-title :title="title" data-testid="title-input" @title-input="handleTitleInput" />
+ <item-title :title="initialTitle" data-testid="title-input" @title-input="handleTitleInput" />
<div>
<gl-loading-icon
v-if="$apollo.queries.workItemTypes.loading"
- size="md"
+ size="lg"
data-testid="loading-types"
/>
<gl-form-select
diff --git a/app/assets/javascripts/work_items/pages/work_item_root.vue b/app/assets/javascripts/work_items/pages/work_item_root.vue
index b8f2bcff25d..6dc3dc3b3c9 100644
--- a/app/assets/javascripts/work_items/pages/work_item_root.vue
+++ b/app/assets/javascripts/work_items/pages/work_item_root.vue
@@ -1,26 +1,70 @@
<script>
+import { GlAlert } from '@gitlab/ui';
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
import WorkItemDetail from '../components/work_item_detail.vue';
+import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql';
export default {
components: {
+ GlAlert,
WorkItemDetail,
},
+ inject: ['issuesListPath'],
props: {
id: {
type: String,
required: true,
},
},
+ data() {
+ return {
+ error: '',
+ };
+ },
computed: {
gid() {
return convertToGraphQLId(TYPE_WORK_ITEM, this.id);
},
},
+ methods: {
+ deleteWorkItem() {
+ this.$apollo
+ .mutate({
+ mutation: deleteWorkItemMutation,
+ variables: {
+ input: {
+ id: this.gid,
+ },
+ },
+ })
+ .then(({ data: { workItemDelete, errors } }) => {
+ if (errors?.length) {
+ throw new Error(errors[0].message);
+ }
+
+ if (workItemDelete?.errors.length) {
+ throw new Error(workItemDelete.errors[0]);
+ }
+
+ this.$toast.show(s__('WorkItem|Work item deleted'));
+ visitUrl(this.issuesListPath);
+ })
+ .catch((e) => {
+ this.error =
+ e.message ||
+ s__('WorkItem|Something went wrong when deleting the work item. Please try again.');
+ });
+ },
+ },
};
</script>
<template>
- <work-item-detail :work-item-id="gid" />
+ <div>
+ <gl-alert v-if="error" variant="danger" @dismiss="error = ''">{{ error }}</gl-alert>
+ <work-item-detail :work-item-id="gid" @deleteWorkItem="deleteWorkItem" />
+ </div>
</template>
diff --git a/app/assets/javascripts/work_items/router/index.js b/app/assets/javascripts/work_items/router/index.js
index 142fab8cfa6..2b39a298720 100644
--- a/app/assets/javascripts/work_items/router/index.js
+++ b/app/assets/javascripts/work_items/router/index.js
@@ -1,8 +1,10 @@
+import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueRouter from 'vue-router';
import { joinPaths } from '~/lib/utils/url_utility';
import { routes } from './routes';
+Vue.use(GlToast);
Vue.use(VueRouter);
export function createRouter(fullPath) {
diff --git a/app/assets/javascripts/work_items_hierarchy/components/app.vue b/app/assets/javascripts/work_items_hierarchy/components/app.vue
index 621cfe5bace..779bd27516a 100644
--- a/app/assets/javascripts/work_items_hierarchy/components/app.vue
+++ b/app/assets/javascripts/work_items_hierarchy/components/app.vue
@@ -1,6 +1,6 @@
<script>
import { GlBanner } from '@gitlab/ui';
-import Cookies from 'js-cookie';
+import Cookies from '~/lib/utils/cookies';
import { parseBoolean } from '~/lib/utils/common_utils';
import RESPONSE from '../static_response';
import { WORK_ITEMS_SURVEY_COOKIE_NAME, workItemTypes } from '../constants';
diff --git a/app/assets/stylesheets/bootstrap_migration_components.scss b/app/assets/stylesheets/bootstrap_migration_components.scss
index b6cecbe5806..676e69707c7 100644
--- a/app/assets/stylesheets/bootstrap_migration_components.scss
+++ b/app/assets/stylesheets/bootstrap_migration_components.scss
@@ -26,7 +26,7 @@ input[type='file'] {
line-height: 1;
}
-.form-group.row .col-form-label {
+.form-group.row > .col-form-label {
// Bootstrap 4 aligns labels to the left
// for horizontal forms
@include media-breakpoint-up(md) {
@@ -118,7 +118,14 @@ input[type='file'] {
margin-bottom: 16px;
.well-segment {
- padding: 16px;
+ padding: 1rem;
+
+ &.pipeline-info {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ }
&:not(:last-of-type) {
border-bottom: 1px solid $well-inner-border;
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 7f498b79d33..870ed50c6eb 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -13,6 +13,11 @@
}
}
+ img.ProseMirror-selectednode {
+ outline: 3px solid rgba($blue-400, 0.48);
+ outline-offset: -3px;
+ }
+
ul[data-type='taskList'] {
list-style: none;
padding: 0;
@@ -121,3 +126,7 @@
border: 1px solid $black-transparent;
background-color: var(--gl-color-chip-color);
}
+
+.bubble-menu-form {
+ width: 320px;
+}
diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/components/dashboard_skeleton.scss
index 09ba89c0782..1dcaa47470b 100644
--- a/app/assets/stylesheets/components/dashboard_skeleton.scss
+++ b/app/assets/stylesheets/components/dashboard_skeleton.scss
@@ -48,8 +48,7 @@
}
}
- &-header,
- &-footer {
+ &-header {
&-failed {
background-color: $red-100;
}
diff --git a/app/assets/stylesheets/components/design_management/design.scss b/app/assets/stylesheets/components/design_management/design.scss
index a9be1d89495..b8bd1000bfd 100644
--- a/app/assets/stylesheets/components/design_management/design.scss
+++ b/app/assets/stylesheets/components/design_management/design.scss
@@ -7,7 +7,7 @@ $t-gray-a-16-design-pin: rgba($black, 0.16);
}
.design-detail {
- background-color: rgba($black, 0.9);
+ background-color: rgba($modal-backdrop-bg, $modal-backdrop-opacity);
.with-performance-bar & {
top: 35px;
diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss
index 98a7ea5792b..c1c68f64d86 100644
--- a/app/assets/stylesheets/components/whats_new.scss
+++ b/app/assets/stylesheets/components/whats_new.scss
@@ -47,12 +47,6 @@
margin-top: calc(#{$performance-bar-height} + #{$system-header-height} + #{$header-height});
}
-.gl-badge.whats-new-item-badge {
- background-color: $purple-light;
- color: $purple;
- @include gl-font-weight-bold;
-}
-
.whats-new-item-title-link {
&:hover,
&:focus,
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index dc08c816d7d..00cc3409fa7 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -118,3 +118,11 @@ a {
}
}
}
+
+.tanuki-logo {
+ width: 210px;
+ height: 210px;
+ max-width: 40vw;
+ display: block;
+ margin: map-get($spacing-scale, 4) auto;
+}
diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss
index 58f986ec0ae..a0bfca79dc3 100644
--- a/app/assets/stylesheets/framework/broadcast_messages.scss
+++ b/app/assets/stylesheets/framework/broadcast_messages.scss
@@ -14,11 +14,14 @@
}
.broadcast-banner-message {
- text-align: center;
-
p {
margin-bottom: 0;
}
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ }
}
.broadcast-notification-message {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 9cebd4f49a4..33522c66024 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -121,6 +121,10 @@
@include btn-color($white, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-normal, $gl-text-color);
}
+@mixin btn-purple {
+ @include btn-color($purple-700, $purple-800, $purple-800, $purple-900, $purple-900, $purple-950, $white);
+}
+
@mixin btn-with-margin {
margin-left: $btn-side-margin;
float: left;
@@ -194,6 +198,10 @@
@include btn-red;
}
+ &.btn-purple {
+ @include btn-purple;
+ }
+
&.btn-grouped {
@include btn-with-margin;
}
@@ -261,7 +269,7 @@
.btn-block {
width: 100%;
margin: 0;
- margin-bottom: 15px;
+ @include gl-mb-5;
&.btn {
padding: 6px 0;
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index f0495fdc94e..7a77256398e 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -34,7 +34,7 @@
@media (min-width: map-get($grid-breakpoints, md)) {
// The `+11` is to ensure the file header border shows when scrolled -
// the bottom of the compare-versions header and the top of the file header
- $mr-file-header-top: calc(#{$mr-version-controls-height} + #{$header-height} + #{$mr-tabs-height} + 11px);
+ $mr-file-header-top: calc(#{$header-height} + #{$mr-tabs-height});
position: -webkit-sticky;
position: sticky;
@@ -582,25 +582,6 @@ table.code {
}
}
-.diff-expansion-cell {
- flex: 1 1;
- min-width: max-content;
-}
-
-.diff-expansion-cell-middle {
- flex: 0 1 max-content;
-}
-
-@media only screen and (min-width: $breakpoint-xl) {
- .diff-expansion-cell-start {
- text-align: right;
- }
-
- .diff-expansion-cell-end {
- text-align: left;
- }
-}
-
// Merge request diff grid layout
.diff-grid {
.diff-td {
@@ -612,6 +593,10 @@ table.code {
.diff-grid-row {
display: grid;
grid-template-columns: 1fr 1fr;
+
+ &.diff-grid-row-full {
+ grid-template-columns: 1fr;
+ }
}
.diff-grid-left,
@@ -626,6 +611,14 @@ table.code {
grid-template-columns: 50px 1fr !important;
}
+ .diff-grid-2-col {
+ grid-template-columns: 100px 1fr !important;
+
+ &.parallel {
+ grid-template-columns: 50px 1fr !important;
+ }
+ }
+
&.inline-diff-view .diff-grid-3-col {
grid-template-columns: 50px 50px 1fr !important;
}
@@ -1209,3 +1202,10 @@ table.code {
position: absolute;
bottom: 100vh;
}
+
+.diff-line-expand-button {
+ &:hover,
+ &:focus {
+ @include gl-bg-gray-200;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index e378fcb6129..f322c6c8929 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -376,14 +376,12 @@ span.idiff {
a {
color: $gl-text-color;
}
+}
- @include media-breakpoint-down(md) {
- .file-actions {
- margin-top: $gl-padding-8;
-
- .btn {
- margin-bottom: $gl-padding-8;
- }
+.blob-content-holder .file-actions {
+ @include media-breakpoint-down(sm) {
+ .btn {
+ margin-bottom: $gl-padding-8;
}
}
}
@@ -452,7 +450,6 @@ span.idiff {
.note-container {
.user-avatar-link.new-comment {
position: absolute;
- margin: 40px $gl-padding 0 116px;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5dd71cec8d1..37b61d36911 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -49,7 +49,7 @@
margin: 0 0 10px;
}
- .dropdown-menu-toggle,
+ .dropdown-menu-toggle.dropdown-menu-toggle,
.update-issues-btn .btn {
width: 100%;
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4d0d64ae723..50783433c3d 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -83,7 +83,6 @@ label {
margin-right: 0;
.form-control {
- height: 29px;
background: $white;
font-family: $monospace-font;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f44123fc2ed..f76a0cbbae8 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -10,17 +10,6 @@
right: 0;
border-radius: 0;
- .logo-text {
- line-height: initial;
-
- svg {
- width: 55px;
- height: 14px;
- margin: 0;
- fill: $white;
- }
- }
-
.close-icon {
display: none;
}
@@ -33,14 +22,6 @@
min-height: $header-height;
padding-left: 0;
- .title-container {
- display: flex;
- align-items: stretch;
- flex: 1 1 auto;
- padding-top: 0;
- overflow: visible;
- }
-
.title {
padding-right: 0;
color: currentColor;
@@ -71,10 +52,14 @@
display: flex;
align-items: center;
padding: 2px 8px;
- margin: 5px 2px 5px -8px;
+ margin: 4px 2px 4px -12px;
border-radius: $border-radius-default;
}
+ .canary-badge {
+ margin-left: -8px;
+ }
+
.project-item-select {
right: auto;
left: 0;
@@ -127,7 +112,7 @@
border-radius: 0;
min-width: 45px;
padding: 0;
- margin: $gl-padding-8 -7px $gl-padding-8 0;
+ margin: $gl-padding-8 $gl-padding-8 $gl-padding-8 0;
font-size: 14px;
text-align: center;
color: currentColor;
@@ -353,6 +338,14 @@
}
}
+ &:last-child {
+ > a {
+ font-weight: 600;
+ line-height: 16px;
+ color: $gl-text-color;
+ }
+ }
+
> a {
font-size: 12px;
color: currentColor;
@@ -390,17 +383,6 @@
margin-left: auto;
}
-.breadcrumbs-sub-title {
- margin: 0;
- font-size: 12px;
- font-weight: 600;
- line-height: 16px;
-
- a {
- color: $gl-text-color;
- }
-}
-
.btn-sign-in {
background-color: $indigo-100;
color: $indigo-900;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 0aeb7208c59..ca0240b6a65 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -3,6 +3,17 @@
svg {
fill: $green-500;
}
+
+ &.interactive {
+ &:hover {
+ background: $green-500;
+
+ svg {
+ --svg-status-bg: #{$green-100};
+ box-shadow: 0 0 0 1px $green-500;
+ }
+ }
+ }
}
.ci-status-icon-error,
@@ -10,6 +21,17 @@
svg {
fill: $red-500;
}
+
+ &.interactive {
+ &:hover {
+ background: $red-500;
+
+ svg {
+ --svg-status-bg: #{$red-100};
+ box-shadow: 0 0 0 1px $red-500;
+ }
+ }
+ }
}
.ci-status-icon-pending,
@@ -19,11 +41,33 @@
svg {
fill: $orange-500;
}
+
+ &.interactive {
+ &:hover {
+ background: $orange-500;
+
+ svg {
+ --svg-status-bg: #{$orange-100};
+ box-shadow: 0 0 0 1px $orange-500;
+ }
+ }
+ }
}
.ci-status-icon-running {
svg {
- fill: $blue-400;
+ fill: $blue-500;
+ }
+
+ &.interactive {
+ &:hover {
+ background: $blue-500;
+
+ svg {
+ --svg-status-bg: #{$blue-100};
+ box-shadow: 0 0 0 1px $blue-500;
+ }
+ }
}
}
@@ -32,7 +76,18 @@
.ci-status-icon-scheduled,
.ci-status-icon-manual {
svg {
- fill: $gl-text-color;
+ fill: $gray-900;
+ }
+
+ &.interactive {
+ &:hover {
+ background: $gray-900;
+
+ svg {
+ --svg-status-bg: #{$gray-100};
+ box-shadow: 0 0 0 1px $gray-900;
+ }
+ }
}
}
@@ -42,7 +97,18 @@
.ci-status-icon-skipped,
.ci-status-icon-notfound {
svg {
- fill: var(--gray-400, $gray-400);
+ fill: $gray-500;
+ }
+
+ &.interactive {
+ &:hover {
+ background: $gray-500;
+
+ svg {
+ --svg-status-bg: #{$gray-100};
+ box-shadow: 0 0 0 1px $gray-500;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
index c5feefb8c54..1845438eedb 100644
--- a/app/assets/stylesheets/framework/logo.scss
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -9,19 +9,16 @@
}
.tanuki-logo {
- .tanuki-left-ear,
- .tanuki-right-ear,
- .tanuki-nose {
+ .tanuki {
@include tanuki-logo-colors($tanuki-red);
}
- .tanuki-left-eye,
- .tanuki-right-eye {
+ .left-cheek,
+ .right-cheek {
@include tanuki-logo-colors($tanuki-orange);
}
- .tanuki-left-cheek,
- .tanuki-right-cheek {
+ .chin {
@include tanuki-logo-colors($tanuki-yellow);
}
@@ -31,98 +28,54 @@
@include webkit-prefix(animation-iteration-count, infinite);
}
- .tanuki-left-cheek {
- @include include-keyframes(animate-tanuki-left-cheek) {
+ .tanuki {
+ @include include-keyframes(animate-tanuki-base) {
0%,
- 10%,
- 100% {
- fill: lighten($tanuki-yellow, 25%);
- }
-
- 90% {
- fill: $tanuki-yellow;
- }
- }
- }
-
- .tanuki-left-eye {
- @include include-keyframes(animate-tanuki-left-eye) {
- 10%,
- 80% {
- fill: $tanuki-orange;
- }
-
- 20%,
- 90% {
- fill: lighten($tanuki-orange, 25%);
- }
- }
- }
-
- .tanuki-left-ear {
- @include include-keyframes(animate-tanuki-left-ear) {
- 10%,
- 80% {
+ 50% {
fill: $tanuki-red;
}
- 20%,
- 90% {
+ 25% {
fill: lighten($tanuki-red, 25%);
}
}
}
- .tanuki-nose {
- @include include-keyframes(animate-tanuki-nose) {
- 20%,
- 70% {
- fill: $tanuki-red;
- }
-
- 30%,
- 80% {
- fill: lighten($tanuki-red, 25%);
- }
- }
- }
-
- .tanuki-right-eye {
- @include include-keyframes(animate-tanuki-right-eye) {
- 30%,
- 60% {
+ .right-cheek {
+ @include include-keyframes(animate-tanuki-right-cheek) {
+ 25%,
+ 75% {
fill: $tanuki-orange;
}
- 40%,
- 70% {
+ 50% {
fill: lighten($tanuki-orange, 25%);
}
}
}
- .tanuki-right-ear {
- @include include-keyframes(animate-tanuki-right-ear) {
- 30%,
- 60% {
- fill: $tanuki-red;
+ .chin {
+ @include include-keyframes(animate-tanuki-chin) {
+ 50%,
+ 100% {
+ fill: $tanuki-yellow;
}
- 40%,
- 70% {
- fill: lighten($tanuki-red, 25%);
+ 75% {
+ fill: lighten($tanuki-yellow, 25%);
}
}
}
- .tanuki-right-cheek {
- @include include-keyframes(animate-tanuki-right-cheek) {
- 40% {
- fill: $tanuki-yellow;
+ .left-cheek {
+ @include include-keyframes(animate-tanuki-left-cheek) {
+ 25%,
+ 75% {
+ fill: $tanuki-orange;
}
- 60% {
- fill: lighten($tanuki-yellow, 25%);
+ 100% {
+ fill: lighten($tanuki-orange, 25%);
}
}
}
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 8cad55f414a..549b61aedae 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -174,6 +174,10 @@
width: 100%;
}
+ .btn.dropdown-toggle-split {
+ margin-left: 1px;
+ }
+
/* This resets the width of the control so that the search button doesn't wrap */
.gl-search-box-by-click .form-control {
width: 1%;
@@ -368,7 +372,7 @@
.project-item-select-holder.btn-group {
.new-project-item-select-button {
- max-width: 44px;
+ max-width: 32px;
}
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index d270f802c56..dd9581c4692 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -19,13 +19,23 @@
.right-sidebar-collapsed {
padding-right: 0;
- @include media-breakpoint-up(sm) {
- &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
- padding-right: $gutter-collapsed-width;
+ &:not(.is-merge-request) {
+ @include media-breakpoint-up(sm) {
+ &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
+ padding-right: $gutter-collapsed-width;
+ }
+
+ .merge-request-tabs-holder.affix {
+ right: $gutter-collapsed-width;
+ }
}
+ }
- .merge-request-tabs-holder.affix {
- right: $gutter-collapsed-width;
+ &.is-merge-request {
+ @include media-breakpoint-up(md) {
+ .content-wrapper {
+ padding-right: $gutter-collapsed-width;
+ }
}
}
@@ -49,6 +59,18 @@
padding-right: 0;
z-index: $zindex-dropdown-menu;
+ &.right-sidebar-merge-requests {
+ width: 270px;
+
+ @include media-breakpoint-up(md) {
+ z-index: auto;
+ }
+
+ .shortcut-sidebar-dropdown-toggle {
+ margin-right: 0 !important;
+ }
+ }
+
@include media-breakpoint-only(sm) {
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter-collapsed-width;
@@ -71,12 +93,20 @@
}
.right-sidebar {
- border-left: 1px solid $border-color;
+ border-left: 1px solid $gray-100;
- .sidebar-container,
- .issuable-sidebar {
- // Add 100px so that potentially visible vertical scroll bar is hidden
- width: calc(100% + 100px);
+ &.right-sidebar-merge-requests {
+ @include media-breakpoint-up(md) {
+ border-left: 0;
+ }
+ }
+
+ &:not(.right-sidebar-merge-requests) {
+ .sidebar-container,
+ .issuable-sidebar {
+ // Add 100px so that potentially visible vertical scroll bar is hidden
+ width: calc(100% + 100px);
+ }
}
}
@@ -135,7 +165,11 @@
// rest of the sidebar, and could be removed once the sidebar has been fully converted to use
// gitlab-ui components.
.title .gl-button {
- color: $gl-text-color;
+ font-weight: $gl-font-weight-bold;
+
+ .gl-button {
+ color: $gl-text-color;
+ }
}
}
@@ -227,6 +261,10 @@
margin-right: -$gl-spacing-scale-2;
}
+.issuable-sidebar.is-merge-request .edit-link {
+ margin-right: 0;
+}
+
.assignee-grid {
grid-template-areas: ' attention user';
grid-template-columns: min-content 1fr;
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 6348703e9e1..f39d53c5b1c 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -10,9 +10,6 @@ table {
* color palette in https://gitlab.com/gitlab-org/gitlab/-/issues/213570
*
* The overwrites here affected the following areas:
- * - The security dashboard tables. When removing
- * this code, table-th-transparent and original-text-color classes should
- * be removed there.
* - The subscription seats table. When removing this code, the .seats-table
* <th> and margin overrides should be removed there.
*
@@ -23,8 +20,16 @@ table {
@include gl-text-gray-500;
}
- .md &:not(.code),
&.table {
+ .thead-white {
+ th {
+ background-color: $white;
+ }
+ }
+ }
+
+ .md &:not(.code),
+ &.table:not(.gl-table) {
margin-bottom: $gl-padding;
.dropdown-menu a {
@@ -58,23 +63,12 @@ table {
&.wide {
width: 55%;
}
-
- &.table-th-transparent {
- background: none;
- color: $gl-text-color-secondary;
- }
-
- &.original-gl-th {
- @include gl-text-gray-500;
- border-bottom: 1px solid $cycle-analytics-light-gray;
- }
}
}
.thead-white {
th {
- background-color: $white;
color: $gl-text-color-secondary;
border-top: 0;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 8e3b34e4eaf..bc649b6407d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -453,7 +453,7 @@ $border-radius-small: 2px;
$border-radius-large: 8px;
$default-icon-size: 16px;
$layout-link-gray: #7e7c7c;
-$btn-side-margin: 10px;
+$btn-side-margin: $grid-size;
$btn-sm-side-margin: 7px;
$btn-margin-5: 5px;
$count-arrow-border: #dce0e5;
@@ -658,7 +658,6 @@ $calendar-user-contrib-text: #959494;
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
$cycle-analytics-big-font: 19px;
-$cycle-analytics-light-gray: #bfbfbf;
$cycle-analytics-dismiss-icon-color: #b2b2b2;
/*
@@ -934,7 +933,6 @@ $issues-analytics-popover-boarder-color: rgba(0, 0, 0, 0.15);
Merge requests
*/
$mr-tabs-height: 48px;
-$mr-version-controls-height: 56px;
/*
Compare Branches
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index b796f04750b..cfd215b81b8 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -39,7 +39,7 @@
.icon-container {
display: inline-block;
- margin-right: 8px;
+ margin: 0 0.5rem 0 0.25rem;
svg {
position: relative;
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
index bd327082e20..433141ae690 100644
--- a/app/assets/stylesheets/highlight/common.scss
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -19,7 +19,17 @@
background: $dark-diff-match-color;
}
-@mixin diff-expansion($background, $border, $link) {
+@mixin diff-expansion($background, $color, $hover-background, $hover-color) {
+ background-color: $background;
+ color: $color;
+
+ &:hover {
+ background-color: $hover-background;
+ color: $hover-color;
+ }
+}
+
+@mixin old-diff-expansion($background, $border, $link) {
background-color: $background;
.diff-td,
@@ -49,6 +59,13 @@
}
}
+
+@mixin dark-diff-expansion-line {
+ &.expansion .diff-td {
+ background-color: $dark-diff-match-color;
+ }
+}
+
@mixin line-coverage-border-color($coverage, $no-coverage) {
transition: border-left 0.1s ease-out;
@@ -121,7 +138,8 @@
@include gl-mr-2;
@include gl-w-4;
@include gl-h-4;
- @include gl-float-left;
+ @include gl-absolute;
+ @include gl-left-3;
background-color: $color;
mask-image: asset_url('icons-stacked.svg#link');
mask-repeat: no-repeat;
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index c51b1f04757..0eeebdb2e7a 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -154,8 +154,12 @@ $dark-il: #de935f;
color: $dark-line-color;
}
- .line_expansion {
- @include diff-expansion($dark-main-bg, $dark-border, $dark-na);
+ .old-line_expansion {
+ @include old-diff-expansion($dark-main-bg, $dark-border, $dark-na);
+ }
+
+ .diff-line-expand-button {
+ @include diff-expansion($gray-600, $gray-200, $gray-300, $white);
}
// Diff line
@@ -166,6 +170,10 @@ $dark-il: #de935f;
@include dark-diff-match-line;
}
+ &.diff-grid-row {
+ @include dark-diff-expansion-line;
+ }
+
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line-codequality.hll:not(.empty-cell),
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 226bb44f0e7..b8cd97d6504 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -1,6 +1,7 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
@import '../common';
+@import 'highlight.js/styles/base16/monokai.css';
/*
* Monokai Colors
@@ -124,8 +125,12 @@ $monokai-gh: #75715e;
color: $monokai-text-color;
}
- .line_expansion {
- @include diff-expansion($monokai-bg, $monokai-border, $monokai-k);
+ .old-line_expansion {
+ @include old-diff-expansion($monokai-bg, $monokai-border, $monokai-k);
+ }
+
+ .diff-line-expand-button {
+ @include diff-expansion($gray-600, $gray-200, $gray-300, $white);
}
// Diff line
@@ -136,6 +141,10 @@ $monokai-gh: #75715e;
@include dark-diff-match-line;
}
+ &.diff-grid-row {
+ @include dark-diff-expansion-line;
+ }
+
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line-codequality.hll:not(.empty-cell),
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 7a36aba8be7..99a3de23c26 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -44,10 +44,15 @@
color: $gl-text-color;
}
- .line_expansion {
- @include diff-expansion($gray-light, $white-normal, $gl-text-color);
+ .old-line_expansion {
+ @include old-diff-expansion($gray-light, $white-normal, $gl-text-color);
}
+ .diff-line-expand-button {
+ @include diff-expansion($gray-100, $gray-700, $gray-200, $gray-800);
+ }
+
+
// Diff line
$none-expanded-border: #e0e0e0;
$none-expanded-bg: #e0e0e0;
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index acd401e1694..55d17b8f1d2 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -1,6 +1,7 @@
/* https://gist.github.com/qguv/7936275 */
@import '../common';
+@import 'highlight.js/styles/base16/solarized-dark.css';
/*
* Solarized dark colors
@@ -127,8 +128,12 @@ $solarized-dark-il: #2aa198;
color: $solarized-dark-pre-color;
}
- .line_expansion {
- @include diff-expansion($solarized-dark-line-bg, $solarized-dark-border, $solarized-dark-kd);
+ .old-line_expansion {
+ @include old-diff-expansion($solarized-dark-line-bg, $solarized-dark-border, $solarized-dark-kd);
+ }
+
+ .diff-line-expand-button {
+ @include diff-expansion(lighten($solarized-dark-pre-bg, 10%), $gray-200, lighten($solarized-dark-pre-bg, 20%), $white);
}
// Diff line
@@ -139,6 +144,10 @@ $solarized-dark-il: #2aa198;
@include dark-diff-match-line;
}
+ &.diff-grid-row {
+ @include dark-diff-expansion-line;
+ }
+
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line-codequality.hll:not(.empty-cell),
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index ddcecc4cbcf..72b961097e4 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -1,6 +1,7 @@
/* https://gist.github.com/qguv/7936275 */
@import '../common';
+@import 'highlight.js/styles/base16/solarized-light.css';
/*
* Solarized light syntax colors
@@ -133,9 +134,13 @@ $solarized-light-il: #2aa198;
background-color: $solarized-light-pre-bg;
color: $solarized-light-pre-color;
}
+
+ .old-line_expansion {
+ @include old-diff-expansion($solarized-light-line-bg, $solarized-light-border, $solarized-light-kd);
+ }
- .line_expansion {
- @include diff-expansion($solarized-light-line-bg, $solarized-light-border, $solarized-light-kd);
+ .diff-line-expand-button {
+ @include diff-expansion($gray-100, $gray-700, $gray-200, $gray-800);
}
// Diff line
@@ -146,6 +151,10 @@ $solarized-light-il: #2aa198;
@include match-line;
}
+ &.diff-grid-row.expansion .diff-td {
+ background-color: $solarized-light-matchline-bg;
+ }
+
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line-codequality.hll:not(.empty-cell),
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index 20a36d2e8b1..b984c194033 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -77,6 +77,22 @@ $white-gc-bg: #eaf2f5;
background-color: $gray-light;
}
+@mixin diff-match-line {
+ &.expansion {
+ &.match .diff-td {
+ color: $gray-400;
+ }
+
+ .diff-td {
+ background-color: $gray-50;
+
+ &:first-child {
+ border-color: $gray-100;
+ }
+ }
+ }
+}
+
// Line numbers
.file-line-num {
@include line-number-link($black-transparent);
@@ -117,8 +133,8 @@ pre.code,
color: $white-code-color;
}
-.line_expansion {
- @include diff-expansion($gray-light, $border-color, $blue-600);
+.old-line_expansion {
+ @include old-diff-expansion($gray-light, $border-color, $blue-600);
&.diff-tr:last-child {
border-bottom-right-radius: 4px;
@@ -130,6 +146,10 @@ pre.code,
}
}
+.diff-line-expand-button {
+ @include diff-expansion($gray-100, $gray-700, $gray-200, $gray-800);
+}
+
// Diff line
.line_holder {
&.match .line_content,
@@ -138,6 +158,10 @@ pre.code,
@include match-line;
}
+ &.diff-grid-row {
+ @include diff-match-line;
+ }
+
&:not(.match) .diff-grid-left:hover,
&:not(.match) .diff-grid-right:hover,
&.code-search-line:hover {
diff --git a/app/assets/stylesheets/mailer.scss b/app/assets/stylesheets/mailer.scss
index 5f50489555b..b8cbe64df38 100644
--- a/app/assets/stylesheets/mailer.scss
+++ b/app/assets/stylesheets/mailer.scss
@@ -197,6 +197,5 @@ tr.footer td {
.footer-logo {
width: 90px;
- height: 33px;
}
}
diff --git a/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss b/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss
index ee777820b81..3327f8da632 100644
--- a/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss
+++ b/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss
@@ -13,98 +13,6 @@
}
}
-@mixin mini-pipeline-graph-color(
- $color-background-default,
- $color-background-hover-focus,
- $color-background-active,
- $color-foreground-default,
- $color-foreground-hover-focus,
- $color-foreground-active
-) {
- background-color: $color-background-default;
- border-color: $color-foreground-default;
-
- svg {
- fill: $color-foreground-default;
- }
-
- &:hover,
- &:focus {
- background-color: $color-background-hover-focus;
- border-color: $color-foreground-hover-focus;
-
- svg {
- fill: $color-foreground-hover-focus;
- }
- }
-
- &:active {
- background-color: $color-background-active;
- border-color: $color-foreground-active;
-
- svg {
- fill: $color-foreground-active;
- }
- }
-
- &:focus {
- box-shadow: 0 0 4px 1px $blue-300;
- }
-}
-
-@mixin mini-pipeline-item() {
- border-radius: 100px;
- background-color: var(--white, $white);
- border-width: 1px;
- border-style: solid;
- width: $ci-action-icon-size;
- height: $ci-action-icon-size;
- margin: 0;
- padding: 0;
- position: relative;
- vertical-align: middle;
-
- &:hover,
- &:active,
- &:focus {
- outline: none;
- border-width: 2px;
- }
-
- // Dropdown button animation in mini pipeline graph
- &.ci-status-icon-success {
- @include mini-pipeline-graph-color(var(--white, $white), $green-100, $green-200, $green-500, $green-600, $green-700);
- }
-
- &.ci-status-icon-failed {
- @include mini-pipeline-graph-color(var(--white, $white), $red-100, $red-200, $red-500, $red-600, $red-700);
- }
-
- &.ci-status-icon-pending,
- &.ci-status-icon-waiting-for-resource,
- &.ci-status-icon-success-with-warnings {
- @include mini-pipeline-graph-color(var(--white, $white), $orange-50, $orange-100, $orange-500, $orange-600, $orange-700);
- }
-
- &.ci-status-icon-running {
- @include mini-pipeline-graph-color(var(--white, $white), $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
- }
-
- &.ci-status-icon-canceled,
- &.ci-status-icon-scheduled,
- &.ci-status-icon-disabled,
- &.ci-status-icon-manual {
- @include mini-pipeline-graph-color(var(--white, $white), $gray-500, $gray-700, $gray-900, $gray-950, $black);
- }
-
- &.ci-status-icon-preparing,
- &.ci-status-icon-created,
- &.ci-status-icon-not-found,
- &.ci-status-icon-skipped {
- @include mini-pipeline-graph-color(var(--white, $white), var(--gray-100, $gray-100), var(--gray-200, $gray-200), var(--gray-400, $gray-400), var(--gray-500, $gray-500), var(--gray-600, $gray-600));
- }
-}
-
/**
Action icons inside dropdowns:
- mini graph in pipelines table
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index eecd4954e39..81d35b8bc7b 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -49,6 +49,7 @@
height: calc(100vh - #{$issue-board-list-difference-xs});
overflow-x: scroll;
min-height: 200px;
+ border-left: 8px solid var(--gray-10, $white);
@include media-breakpoint-only(sm) {
height: calc(100vh - #{$issue-board-list-difference-sm});
@@ -131,8 +132,7 @@
.board-inner {
font-size: $issue-boards-font-size;
- background: var(--gray-10, $gray-10);
- border: 1px solid var(--gray-100, $gray-100);
+ background: var(--gray-50, $gray-50);
}
// to highlight columns we have animated pulse of box-shadow
@@ -169,33 +169,7 @@
}
}
-.board-header {
- &.has-border::before {
- border-top: 3px solid;
- border-color: inherit;
- border-top-left-radius: $border-radius-default;
- border-top-right-radius: $border-radius-default;
- content: '';
- position: absolute;
- width: calc(100% + 2px);
- top: 0;
- left: 0;
- margin-top: -1px;
- margin-right: -1px;
- margin-left: -1px;
- padding-top: 1px;
- padding-right: 1px;
- padding-left: 1px;
-
- .board-title {
- padding-top: ($gl-padding - 3px);
- padding-bottom: $gl-padding;
- }
- }
-}
-
.board-title {
- border-bottom: 1px solid var(--gray-100, $gray-100);
height: 3rem;
.max-issue-size::before {
@@ -218,8 +192,7 @@
}
.board-card {
- background: var(--white, $white);
- border: 1px solid var(--gray-100, $gray-100);
+ background: var(--gray-10, $white);
box-shadow: 0 1px 2px rgba(var(--black, $black), 0.1);
line-height: $gl-padding;
list-style: none;
@@ -239,6 +212,10 @@
background-color: var(--blue-50, $blue-50);
}
+ &.sortable-chosen {
+ box-shadow: 0 2px 4px 0 rgba($black, 0.16);
+ }
+
.gl-label {
margin-top: 4px;
margin-right: 4px;
diff --git a/app/assets/stylesheets/page_bundles/dashboard_projects.scss b/app/assets/stylesheets/page_bundles/dashboard_projects.scss
index eb0e1701b7f..5eced37bed3 100644
--- a/app/assets/stylesheets/page_bundles/dashboard_projects.scss
+++ b/app/assets/stylesheets/page_bundles/dashboard_projects.scss
@@ -15,9 +15,9 @@
.blank-state-link {
&:hover {
- background-color: $gray-light;
+ background-color: var(--gray-50, $gray-10);
+ color: var(--gl-text-color, $gl-text-color);
text-decoration: none;
- color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/page_bundles/issues_show.scss b/app/assets/stylesheets/page_bundles/issues_show.scss
new file mode 100644
index 00000000000..9873a0121c0
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/issues_show.scss
@@ -0,0 +1,67 @@
+@import 'mixins_and_variables_and_functions';
+
+.description {
+ ul,
+ ol {
+ /* We're changing list-style-position to inside because the default of outside
+ * doesn't move the negative margin to the left of the bullet. */
+ list-style-position: inside;
+ }
+
+ li {
+ position: relative;
+ /* In the browser, the li element comes after (to the right of) the bullet point, so hovering
+ * over the left of the bullet point doesn't trigger a row hover. To trigger hovering on the
+ * left, we're applying negative margin here to shift the li element left. */
+ margin-inline-start: -1rem;
+ padding-inline-start: 2.5rem;
+
+ .drag-icon {
+ position: absolute;
+ inset-block-start: 0.3rem;
+ inset-inline-start: 1rem;
+ }
+ }
+
+ ul.task-list {
+ > li.task-list-item {
+ /* We're using !important to override the same selector in typography.scss */
+ margin-inline-start: -1rem !important;
+ padding-inline-start: 2.5rem;
+
+ > input.task-list-item-checkbox {
+ position: static;
+ vertical-align: middle;
+ margin-block-start: -2px;
+ }
+ }
+ }
+}
+
+.description.work-items-enabled {
+ ul.task-list {
+ > li.task-list-item {
+ .js-add-task {
+ svg {
+ visibility: hidden;
+ }
+
+ &:focus svg {
+ visibility: visible;
+ }
+ }
+
+ &:hover,
+ &:focus-within {
+ .js-add-task svg {
+ visibility: visible;
+ }
+ }
+ }
+ }
+}
+
+.is-ghost {
+ opacity: 0.3;
+ pointer-events: none;
+}
diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss
index 1c8fd7e2590..f153569f99b 100644
--- a/app/assets/stylesheets/page_bundles/jira_connect.scss
+++ b/app/assets/stylesheets/page_bundles/jira_connect.scss
@@ -17,6 +17,11 @@
@import '@gitlab/ui/src/components/base/table/table';
@import '@gitlab/ui/src/components/base/tooltip/tooltip';
@import '@gitlab/ui/src/components/base/search_box_by_type/search_box_by_type';
+@import '@gitlab/ui/src/components/base/form/form_input/form_input';
+@import '@gitlab/ui/src/components/base/form/form_radio/form_radio';
+@import '@gitlab/ui/src/components/base/form/form_radio_group/form_radio_group';
+@import '@gitlab/ui/src/components/base/form/form_checkbox/form_checkbox';
+@import '@gitlab/ui/src/components/base/form/form_group/form_group';
$header-height: 40px;
@@ -39,9 +44,3 @@ $header-height: 40px;
height: calc(100% - #{$header-height});
max-width: 1000px;
}
-
-// needed for external_link
-svg.s16 {
- width: 16px;
- height: 16px;
-}
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 34a3d936a67..f04cdfba0e4 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -41,7 +41,7 @@ $tabs-holder-z-index: 250;
// If they don't match, the file tree and the diff files stick
// to the top at different heights, which is a bad-looking defect
$diff-file-header-top: 11px;
- $top-pos: calc(#{$header-height} + #{$mr-tabs-height} + #{$mr-version-controls-height} + #{$diff-file-header-top});
+ $top-pos: calc(#{$header-height} + #{$mr-tabs-height} + #{$diff-file-header-top});
position: -webkit-sticky;
position: sticky;
@@ -121,10 +121,6 @@ $tabs-holder-z-index: 250;
@include media-breakpoint-down(sm) {
flex-direction: column;
- .stage-cell .stage-container {
- margin-top: 16px;
- }
-
.dropdown .mini-pipeline-graph-dropdown-menu.dropdown-menu {
transform: initial;
}
@@ -666,22 +662,19 @@ $tabs-holder-z-index: 250;
margin-top: $gl-padding;
position: relative;
- &::before {
+ &:not(:last-child)::before {
content: '';
border-left: 1px solid var(--gray-100, $gray-100);
position: absolute;
left: 28px;
- top: -17px;
+ bottom: -17px;
height: 16px;
}
}
.mr-version-controls {
- position: relative;
- z-index: $tabs-holder-z-index + 10;
background: var(--white, $white);
color: var(--gl-text-color, $gl-text-color);
- margin-top: -1px;
.mr-version-menus-container {
display: flex;
@@ -703,45 +696,26 @@ $tabs-holder-z-index: 250;
}
.content-block {
- padding: $gl-padding;
+ padding: $gl-padding-8 $gl-padding;
border-bottom: 0;
}
.mr-version-dropdown,
.mr-version-compare-dropdown {
- margin: 0 0.5rem;
- }
-
- .dropdown-title {
- color: var(--gl-text-color, $gl-text-color);
- }
-
- // Shortening button height by 1px to make compare-versions
- // header 56px and fit into our 8px design grid
- .btn {
- height: 34px;
- }
-
- @include media-breakpoint-up(md) {
- position: -webkit-sticky;
- position: sticky;
- top: calc(#{$header-height} + #{$mr-tabs-height});
+ margin: 0 $gl-spacing-scale-1;
- .with-system-header & {
- top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height});
- }
-
- .with-system-header.with-performance-bar & {
- top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height} + #{$performance-bar-height});
- }
+ .dropdown-toggle.gl-button {
+ padding: $gl-spacing-scale-2 2px $gl-spacing-scale-2 $gl-spacing-scale-2;
+ font-weight: $gl-font-weight-bold;
- .mr-version-menus-container {
- flex-wrap: nowrap;
+ .gl-button-icon {
+ margin-left: $gl-spacing-scale-1;
+ }
}
+ }
- .with-performance-bar & {
- top: calc(#{$header-height} + #{$performance-bar-height} + #{$mr-tabs-height});
- }
+ .dropdown-title {
+ color: var(--gl-text-color, $gl-text-color);
}
}
@@ -761,3 +735,11 @@ $tabs-holder-z-index: 250;
.attention-request-sidebar-popover {
z-index: 999;
}
+
+.merge-request-overview {
+ @include media-breakpoint-up(md) {
+ display: grid;
+ grid-template-columns: 1fr 270px;
+ grid-gap: 5%;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/milestone.scss b/app/assets/stylesheets/page_bundles/milestone.scss
index aa582db10d2..c401f1a4902 100644
--- a/app/assets/stylesheets/page_bundles/milestone.scss
+++ b/app/assets/stylesheets/page_bundles/milestone.scss
@@ -167,7 +167,7 @@ $status-box-line-height: 26px;
border-bottom: 1px solid var(--border-color, $border-color);
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(md) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
diff --git a/app/assets/stylesheets/page_bundles/pipeline.scss b/app/assets/stylesheets/page_bundles/pipeline.scss
index cbb6d68bf35..e6afc70acbb 100644
--- a/app/assets/stylesheets/page_bundles/pipeline.scss
+++ b/app/assets/stylesheets/page_bundles/pipeline.scss
@@ -139,7 +139,7 @@
}
.gl-downstream-pipeline-job-width {
- width: 170px;
+ width: 8rem;
}
.gl-linked-pipeline-padding {
diff --git a/app/assets/stylesheets/page_bundles/pipeline_editor.scss b/app/assets/stylesheets/page_bundles/pipeline_editor.scss
new file mode 100644
index 00000000000..e167052a3e1
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/pipeline_editor.scss
@@ -0,0 +1,22 @@
+@import 'mixins_and_variables_and_functions';
+
+.file-tree-container {
+ @include gl-w-full;
+
+ @media (min-width: $breakpoint-md) {
+ width: 300px;
+ }
+}
+
+.file-tree-container > div.gl-overflow-y-auto {
+ max-height: 220px;
+
+ @media (min-width: $breakpoint-md) {
+ max-height: 700px;
+ }
+}
+
+.file-tree-includes-link:hover > svg {
+ @include gl-display-block;
+ top: 2px;
+}
diff --git a/app/assets/stylesheets/page_bundles/pipelines.scss b/app/assets/stylesheets/page_bundles/pipelines.scss
index 7b54be5c91f..a225a0f0061 100644
--- a/app/assets/stylesheets/page_bundles/pipelines.scss
+++ b/app/assets/stylesheets/page_bundles/pipelines.scss
@@ -73,36 +73,12 @@
// Mini Pipelines
.stage-cell {
- .mini-pipeline-graph-dropdown-toggle {
- svg {
- height: $ci-action-icon-size;
- width: $ci-action-icon-size;
- position: absolute;
- top: -1px;
- left: -1px;
- z-index: 2;
- overflow: visible;
- }
-
- &:hover,
- &:active,
- &:focus {
- svg {
- top: -2px;
- left: -2px;
- }
- }
- }
-
.stage-container {
- display: inline-block;
- position: relative;
- vertical-align: middle;
- height: $ci-action-icon-size;
- margin: 3px 0;
+ align-items: center;
+ display: inline-flex;
+ .stage-container {
- margin-left: 6px;
+ margin-left: 4px;
}
// Hack to show a button tooltip inline
@@ -118,44 +94,15 @@
&:not(:last-child) {
&::after {
content: '';
- width: 7px;
+ width: 4px;
position: absolute;
- right: -7px;
- top: 11px;
- border-bottom: 2px solid var(--border-color, $border-color);
- }
- }
-
- //delete when all pipelines are updated to new size
- &.mr-widget-pipeline-stages {
- + .stage-container {
- margin-left: 4px;
- }
-
- &:not(:last-child) {
- &::after {
- width: 4px;
- right: -4px;
- top: 11px;
- }
+ right: -4px;
+ border-bottom: 2px solid $gray-200;
}
}
}
}
-// Commit mini pipeline (HAML)
-button.mini-pipeline-graph-dropdown-toggle,
-// GlDropdown mini pipeline (Vue)
-// As the `mini-pipeline-item` mixin specificity is lower
-// than the toggle of dropdown with 'variant="link"' we add
-// classes ".gl-button.btn-link" to make it more specific
-// and avoid having the size overriden
-//
-// See https://gitlab.com/gitlab-org/gitlab/-/issues/320737
-button.gl-button.btn-link.mini-pipeline-graph-dropdown-toggle {
- @include mini-pipeline-item();
-}
-
// Action icons inside dropdowns:
// mini graph in pipelines table
// mini graph in MR widget pipeline
diff --git a/app/assets/stylesheets/page_bundles/terms.scss b/app/assets/stylesheets/page_bundles/terms.scss
index 8eb66e58aed..9dff3e9c99c 100644
--- a/app/assets/stylesheets/page_bundles/terms.scss
+++ b/app/assets/stylesheets/page_bundles/terms.scss
@@ -22,14 +22,6 @@
justify-content: space-between;
line-height: $line-height-base;
- .logo-text {
- width: 55px;
- height: 24px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
-
.navbar-collapse {
padding-right: 0;
flex-grow: 0;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index cc8ea1493fc..afe57bb26e6 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -35,9 +35,6 @@
}
.mr-widget-pipeline-graph {
- display: inline-block;
- vertical-align: middle;
-
.dropdown-menu {
margin-top: 11px;
}
@@ -45,8 +42,6 @@
}
.branch-info .commit-icon {
- margin-right: 8px;
-
svg {
top: 3px;
}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index f237d57aa88..e0319952adb 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -8,7 +8,8 @@
a {
color: $gl-text-color;
- &.link {
+ &.link,
+ &.gl-link {
color: $blue-600;
}
}
@@ -25,6 +26,7 @@
.detail-page-header-body {
position: relative;
display: flex;
+ align-items: center;
flex: 1 1;
min-width: 0;
@@ -38,9 +40,18 @@
align-self: center;
flex: 0 0 auto;
- @include media-breakpoint-down(xs) {
- width: 100%;
- margin-top: 10px;
+ &:not(.is-merge-request) {
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+ margin-top: 10px;
+ }
+ }
+
+ &.is-merge-request {
+ @include media-breakpoint-down(sm) {
+ width: 100%;
+ margin-top: 10px;
+ }
}
}
@@ -56,4 +67,8 @@
.description {
margin-top: 6px;
}
+
+ .author-link {
+ color: $gl-text-color;
+ }
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index c00af802c06..4093ef087dc 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -108,21 +108,13 @@
.merge-icon {
color: $orange-400;
position: absolute;
+ bottom: -3px;
+ right: -3px;
filter: drop-shadow(0 0 0.5px $white) drop-shadow(0 0 1px $white) drop-shadow(0 0 2px $white);
}
}
-.assignee .merge-icon {
- top: calc(50% + 0.25rem);
- left: 1.275rem;
-}
-
-.reviewer .merge-icon {
- bottom: -3px;
- right: -3px;
-}
-
-.right-sidebar {
+@mixin right-sidebar {
position: fixed;
top: $header-height;
// Default value for CSS var must contain a unit
@@ -130,10 +122,23 @@
bottom: var(--review-bar-height, 0px);
right: 0;
transition: width $sidebar-transition-duration;
- background: $gray-light;
+ background-color: $white;
z-index: 200;
overflow: hidden;
+}
+
+.right-sidebar {
+ &:not(.right-sidebar-merge-requests) {
+ @include right-sidebar;
+ }
+
+ &.right-sidebar-merge-requests {
+ @include media-breakpoint-down(sm) {
+ @include right-sidebar;
+ }
+ }
+
@include media-breakpoint-down(sm) {
z-index: 251;
}
@@ -143,10 +148,6 @@
&:hover {
color: $blue-800;
-
- .avatar {
- border-color: rgba($gray-normal, 0.2);
- }
}
}
@@ -176,12 +177,26 @@
}
}
+ &.right-sidebar-merge-requests {
+ .block,
+ .sidebar-contained-width,
+ .issuable-sidebar-header {
+ width: 100%;
+ border-bottom: 0;
+ }
+
+ .block {
+ @include media-breakpoint-up(md) {
+ padding: $gl-spacing-scale-5 0;
+ }
+ }
+ }
+
.block,
.sidebar-contained-width,
.issuable-sidebar-header {
@include clearfix;
padding: $gl-padding 0;
- border-bottom: 1px solid $border-gray-normal;
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter-inner-width;
@@ -218,16 +233,6 @@
}
}
- .cross-project-reference {
- span {
- width: 85%;
- }
-
- button {
- padding: 1px 5px;
- }
- }
-
.selectbox {
display: none;
@@ -242,9 +247,20 @@
.issuable-sidebar {
height: 100%;
- overflow-y: scroll;
- overflow-x: hidden;
- -webkit-overflow-scrolling: touch;
+
+ &:not(.is-merge-request) {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ &.is-merge-request {
+ @include media-breakpoint-down(sm) {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+ }
}
&.affix-top .issuable-sidebar {
@@ -252,7 +268,9 @@
}
&.right-sidebar-expanded {
- width: $gutter-width;
+ &:not(.right-sidebar-merge-requests) {
+ width: $gutter-width;
+ }
.value {
line-height: 1;
@@ -260,6 +278,12 @@
.issuable-sidebar {
padding: 0 20px;
+
+ &.is-merge-request {
+ @include media-breakpoint-up(md) {
+ padding: 0;
+ }
+ }
}
&:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
@@ -302,8 +326,17 @@
/* Extra small devices (phones, less than 768px) */
display: none;
/* Small devices (tablets, 768px and up) */
- @include media-breakpoint-up(sm) {
- display: block;
+
+ &:not(.right-sidebar-merge-requests) {
+ @include media-breakpoint-up(sm) {
+ display: block;
+ }
+ }
+
+ &.right-sidebar-merge-requests {
+ @include media-breakpoint-up(md) {
+ display: block;
+ }
}
width: $gutter-collapsed-width;
@@ -380,10 +413,6 @@
}
}
- .sidebar-avatar-counter {
- padding-top: 2px;
- }
-
.todo-undone {
color: $blue-600;
fill: $blue-600;
@@ -428,6 +457,10 @@
width: 24px;
height: 24px;
border-radius: 12px;
+
+ ~.merge-icon {
+ bottom: 0;
+ }
}
.sidebar-collapsed-user {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index f127b0dc66c..04e0ef6631e 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -307,32 +307,3 @@ ul.related-merge-requests > li gl-emoji {
.issuable-header-slide-leave-to {
transform: translateY(-100%);
}
-
-.description.work-items-enabled {
- ul.task-list {
- > li.task-list-item {
- padding-inline-start: 2.5rem;
-
- .js-add-task {
- svg {
- visibility: hidden;
- }
-
- &:focus svg {
- visibility: visible;
- }
- }
-
- > input.task-list-item-checkbox {
- left: 1.25rem;
- }
-
- &:hover,
- &:focus-within {
- .js-add-task svg {
- visibility: visible;
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 4a3ec5992a5..7f0bdadd2bc 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -45,7 +45,6 @@
.omniauth-container {
box-shadow: 0 0 0 1px $border-color;
border-radius: $border-radius;
- padding: 15px;
.login-heading h3 {
font-weight: $gl-font-weight-normal;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f95cff012d0..0d3ed0e7c71 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -233,8 +233,7 @@ $tabs-holder-z-index: 250;
top: calc(#{$header-height} + #{$system-header-height} + #{$performance-bar-height});
}
- @include media-breakpoint-up(sm) {
- position: -webkit-sticky;
+ @include media-breakpoint-up(md) {
position: sticky;
}
@@ -294,8 +293,7 @@ $tabs-holder-z-index: 250;
justify-content: space-between;
@include media-breakpoint-down(xs) {
- .discussion-filter-container,
- .line-resolve-all-container {
+ .discussion-filter-container {
margin-bottom: $gl-padding-4;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index d8c3851748d..1949603b416 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -840,35 +840,6 @@ $system-note-svg-size: 16px;
}
}
-.line-resolve-all-container {
- > div {
- white-space: nowrap;
- }
-
- .btn-group .btn:first-child {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
-}
-
-.line-resolve-all {
- vertical-align: middle;
- display: inline-block;
- padding: $gl-padding-8 $gl-padding-12;
- background-color: $gray-light;
- border: 1px solid $border-color;
- border-right: 0;
- border-radius: $border-radius-default;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- font-size: $gl-font-size;
- line-height: 1rem;
-
- @include media-breakpoint-down(xs) {
- flex: 1;
- }
-}
-
.line-resolve-btn {
position: relative;
top: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8034389adc8..3b76130dd1a 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -548,15 +548,6 @@ pre.light-well {
}
}
-.git-clone-holder,
-.mobile-git-clone {
- .btn {
- .icon {
- fill: $white;
- }
- }
-}
-
.new-protected-branch,
.new-protected-tag {
label {
@@ -713,22 +704,6 @@ pre.light-well {
margin-bottom: 10px;
}
-.service-installation {
- padding: 32px;
- margin: 32px;
- border-radius: 3px;
- background-color: $white;
-
- h3 {
- margin-top: 0;
- }
-
- hr {
- margin: 32px 0;
- border-color: $border-color;
- }
-}
-
.project-badge {
opacity: 0.9;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 18a0f119edf..8755db83d35 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -2,6 +2,11 @@ $search-dropdown-max-height: 400px;
$search-avatar-size: 16px;
$search-sidebar-min-width: 240px;
$search-sidebar-max-width: 300px;
+$search-input-field-x-min-width: 200px;
+$search-input-field-min-width: 320px;
+$search-input-field-max-width: 600px;
+
+$border-radius-medium: 3px;
.search-results {
.search-result-row {
@@ -37,8 +42,44 @@ input[type='checkbox']:hover {
0 0 0 1px lighten($dropdown-input-focus-shadow, 20%);
}
+.header-content {
+ .header-search-new {
+ max-width: $search-input-field-max-width;
+ }
+
+ &.header-search-is-active {
+ .navbar-collapse {
+ flex-grow: 1;
+ }
+
+ @include media-breakpoint-down(lg) {
+ .title-container {
+ flex: 0;
+ overflow: hidden;
+ }
+ }
+
+ @include media-breakpoint-up(xl) {
+ .navbar-nav {
+ padding-left: 1rem;
+ }
+ }
+ }
+}
+
+// This is a temporary workaround!
+// the button in GitLab UI Search components need to be updated to not be the small size
+// see in Figma: https://www.figma.com/file/qEddyqCrI7kPSBjGmwkZzQ/Component-library?node-id=43905%3A45540
+.header-search .gl-search-box-by-type-clear.btn-sm {
+ padding: 0.5rem !important;
+}
+
.header-search {
- width: 320px;
+ min-width: $search-input-field-min-width;
+
+ @include media-breakpoint-between(md, lg) {
+ min-width: $search-input-field-x-min-width;
+ }
input,
svg {
@@ -66,9 +107,10 @@ input[type='checkbox']:hover {
border-width: 0;
border-style: solid;
border-image: none;
- border-radius: 3px;
+ border-radius: $border-radius-medium;
box-shadow: none;
white-space: pre-wrap;
+ box-sizing: border-box;
// Safari
word-wrap: break-word;
overflow-wrap: break-word;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 33c66648718..a9fbff8958d 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -6,10 +6,6 @@
.nav-block {
margin: 16px 0;
- .btn svg {
- color: $gl-text-color-secondary;
- }
-
.tree-ref-holder {
margin-right: 15px;
}
@@ -100,7 +96,7 @@
}
}
- .tree-table {
+ table.tree-table {
margin-bottom: 0;
tr {
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 62d45332204..001431e517b 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -12,6 +12,7 @@ body.gl-dark {
--gl-text-color: #fafafa;
--border-color: #4f4f4f;
--black: #fff;
+ --nav-active-bg: rgba(255, 255, 255, 0.08);
}
:root {
--white: #333;
@@ -350,9 +351,6 @@ h1 {
.d-lg-none {
display: none !important;
}
- .d-lg-block {
- display: block !important;
- }
}
.sr-only {
position: absolute;
@@ -393,8 +391,7 @@ a.gl-badge.badge-info:active {
background-color: #0b5cad;
}
a.gl-badge.badge-info:active {
- box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
- 0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
+ box-shadow: 0 0 0 1px #333, 0 0 0 3px #1f75cb;
outline: none;
}
.gl-badge.badge-success {
@@ -407,8 +404,7 @@ a.gl-badge.badge-success:active {
background-color: #24663b;
}
a.gl-badge.badge-success:active {
- box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
- 0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
+ box-shadow: 0 0 0 1px #333, 0 0 0 3px #1f75cb;
outline: none;
}
.gl-badge.badge-warning {
@@ -421,8 +417,7 @@ a.gl-badge.badge-warning:active {
background-color: #8f4700;
}
a.gl-badge.badge-warning:active {
- box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
- 0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
+ box-shadow: 0 0 0 1px #333, 0 0 0 3px #1f75cb;
outline: none;
}
.gl-button .gl-badge {
@@ -487,9 +482,8 @@ a.gl-badge.badge-warning:active {
}
.gl-link:active {
text-decoration: underline;
- box-shadow: 0 0 0 1px rgba(51, 51, 51, 0.4),
- 0 0 0 4px rgba(66, 143, 220, 0.48);
- outline: none;
+ outline: 2px solid #1f75cb;
+ outline-offset: 2px;
}
.gl-button {
display: inline-flex;
@@ -518,8 +512,7 @@ a.gl-badge.badge-warning:active {
}
.gl-button.gl-button.btn-default:active,
.gl-button.gl-button.btn-default.active {
- box-shadow: inset 0 0 0 2px #bfbfbf, 0 0 0 1px rgba(51, 51, 51, 0.4),
- 0 0 0 4px rgba(66, 143, 220, 0.48);
+ box-shadow: inset 0 0 0 1px #bfbfbf, 0 0 0 1px #333, 0 0 0 3px #1f75cb;
outline: none;
background-color: #404040;
}
@@ -742,7 +735,7 @@ body {
}
}
@media (max-width: 767.98px) {
- .dropdown-menu-toggle {
+ .dropdown-menu-toggle.dropdown-menu-toggle {
width: 100%;
}
}
@@ -770,15 +763,6 @@ input {
right: 0;
border-radius: 0;
}
-.navbar-gitlab .logo-text {
- line-height: initial;
-}
-.navbar-gitlab .logo-text svg {
- width: 55px;
- height: 14px;
- margin: 0;
- fill: #333;
-}
.navbar-gitlab .close-icon {
display: none;
}
@@ -790,13 +774,6 @@ input {
min-height: var(--header-height, 40px);
padding-left: 0;
}
-.navbar-gitlab .header-content .title-container {
- display: flex;
- align-items: stretch;
- flex: 1 1 auto;
- padding-top: 0;
- overflow: visible;
-}
.navbar-gitlab .header-content .title {
padding-right: 0;
color: currentColor;
@@ -817,9 +794,12 @@ input {
display: flex;
align-items: center;
padding: 2px 8px;
- margin: 5px 2px 5px -8px;
+ margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title .canary-badge {
+ margin-left: -8px;
+}
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
@@ -853,7 +833,7 @@ input {
border-radius: 0;
min-width: 45px;
padding: 0;
- margin: 8px -7px 8px 0;
+ margin: 8px 8px 8px 0;
font-size: 14px;
text-align: center;
color: currentColor;
@@ -997,6 +977,16 @@ input {
.top-nav-toggle .dropdown-icon {
margin-right: 0.5rem;
}
+.tanuki-logo .tanuki {
+ fill: #e24329;
+}
+.tanuki-logo .left-cheek,
+.tanuki-logo .right-cheek {
+ fill: #fc6d26;
+}
+.tanuki-logo .chin {
+ fill: #fca326;
+}
.context-header {
position: relative;
margin-right: 2px;
@@ -1513,8 +1503,16 @@ svg.s12 {
svg.s16 {
vertical-align: -3px;
}
+.header-content .header-search-new {
+ max-width: 600px;
+}
.header-search {
- width: 320px;
+ min-width: 320px;
+}
+@media (min-width: 768px) and (max-width: 1199.98px) {
+ .header-search {
+ min-width: 200px;
+ }
}
.header-search.is-not-active::after {
content: "/";
@@ -1535,6 +1533,7 @@ svg.s16 {
border-radius: 3px;
box-shadow: none;
white-space: pre-wrap;
+ box-sizing: border-box;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: keep-all;
@@ -1787,6 +1786,7 @@ body.gl-dark {
--white: #333;
--black: #fff;
--svg-status-bg: #333;
+ --nav-active-bg: rgba(255, 255, 255, 0.08);
}
.nav-sidebar li a {
color: var(--gray-600);
@@ -1862,6 +1862,7 @@ body.gl-dark
}
body.gl-dark .header-search {
background-color: rgba(250, 250, 250, 0.2) !important;
+ border-radius: 4px;
}
body.gl-dark .header-search svg.gl-search-box-by-type-search-icon {
color: rgba(250, 250, 250, 0.8);
@@ -1900,9 +1901,6 @@ body.gl-dark .nav-sidebar .fly-out-top-item .fly-out-top-item-container {
background-color: var(--gray-100, #303030);
color: var(--gray-900, #fafafa);
}
-body.gl-dark .logo-text svg {
- fill: var(--gl-text-color);
-}
body.gl-dark .navbar-gitlab {
background-color: var(--gray-50);
box-shadow: 0 1px 0 0 var(--gray-100);
@@ -2023,6 +2021,7 @@ body.gl-dark {
--white: #333;
--black: #fff;
--svg-status-bg: #333;
+ --nav-active-bg: rgba(255, 255, 255, 0.08);
}
.tab-width-8 {
-moz-tab-size: 8;
@@ -2045,17 +2044,47 @@ body.gl-dark {
.gl-display-none {
display: none;
}
+@media (min-width: 992px) {
+ .gl-lg-display-none\! {
+ display: none !important;
+ }
+}
+.gl-display-flex {
+ display: flex;
+}
+@media (min-width: 992px) {
+ .gl-lg-display-flex {
+ display: flex;
+ }
+}
@media (min-width: 576px) {
.gl-sm-display-block {
display: block;
}
}
+@media (min-width: 992px) {
+ .gl-lg-display-block {
+ display: block;
+ }
+}
+.gl-display-inline-block\! {
+ display: inline-block !important;
+}
+.gl-align-items-stretch {
+ align-items: stretch;
+}
+.gl-flex-grow-1 {
+ flex-grow: 1;
+}
.gl-relative {
position: relative;
}
.gl-absolute {
position: absolute;
}
+.gl-w-full {
+ width: 100%;
+}
.gl-px-3 {
padding-left: 0.5rem;
padding-right: 0.5rem;
@@ -2063,6 +2092,9 @@ body.gl-dark {
.gl-pr-2 {
padding-right: 0.25rem;
}
+.gl-pt-0 {
+ padding-top: 0;
+}
.gl-ml-n2 {
margin-left: -0.25rem;
}
@@ -2073,6 +2105,12 @@ body.gl-dark {
margin-left: 0 !important;
margin-right: 0 !important;
}
+.gl-text-right {
+ text-align: right;
+}
+.gl-white-space-nowrap {
+ white-space: nowrap;
+}
.gl-font-sm {
font-size: 0.75rem;
}
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index a8b7299b935..c42b5554d8d 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -336,9 +336,6 @@ h1 {
.d-lg-none {
display: none !important;
}
- .d-lg-block {
- display: block !important;
- }
}
.sr-only {
position: absolute;
@@ -379,8 +376,7 @@ a.gl-badge.badge-info:active {
background-color: #9dc7f1;
}
a.gl-badge.badge-info:active {
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
- 0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
}
.gl-badge.badge-success {
@@ -393,8 +389,7 @@ a.gl-badge.badge-success:active {
background-color: #91d4a8;
}
a.gl-badge.badge-success:active {
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
- 0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
}
.gl-badge.badge-warning {
@@ -407,8 +402,7 @@ a.gl-badge.badge-warning:active {
background-color: #e9be74;
}
a.gl-badge.badge-warning:active {
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
- 0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
}
.gl-button .gl-badge {
@@ -473,9 +467,8 @@ a.gl-badge.badge-warning:active {
}
.gl-link:active {
text-decoration: underline;
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4),
- 0 0 0 4px rgba(31, 117, 203, 0.48);
- outline: none;
+ outline: 2px solid #428fdc;
+ outline-offset: 2px;
}
.gl-button {
display: inline-flex;
@@ -504,8 +497,7 @@ a.gl-badge.badge-warning:active {
}
.gl-button.gl-button.btn-default:active,
.gl-button.gl-button.btn-default.active {
- box-shadow: inset 0 0 0 2px #5e5e5e, 0 0 0 1px rgba(255, 255, 255, 0.4),
- 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
background-color: #dbdbdb;
}
@@ -728,7 +720,7 @@ body {
}
}
@media (max-width: 767.98px) {
- .dropdown-menu-toggle {
+ .dropdown-menu-toggle.dropdown-menu-toggle {
width: 100%;
}
}
@@ -756,15 +748,6 @@ input {
right: 0;
border-radius: 0;
}
-.navbar-gitlab .logo-text {
- line-height: initial;
-}
-.navbar-gitlab .logo-text svg {
- width: 55px;
- height: 14px;
- margin: 0;
- fill: #fff;
-}
.navbar-gitlab .close-icon {
display: none;
}
@@ -776,13 +759,6 @@ input {
min-height: var(--header-height, 40px);
padding-left: 0;
}
-.navbar-gitlab .header-content .title-container {
- display: flex;
- align-items: stretch;
- flex: 1 1 auto;
- padding-top: 0;
- overflow: visible;
-}
.navbar-gitlab .header-content .title {
padding-right: 0;
color: currentColor;
@@ -803,9 +779,12 @@ input {
display: flex;
align-items: center;
padding: 2px 8px;
- margin: 5px 2px 5px -8px;
+ margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title .canary-badge {
+ margin-left: -8px;
+}
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
margin: 0 2px;
}
@@ -839,7 +818,7 @@ input {
border-radius: 0;
min-width: 45px;
padding: 0;
- margin: 8px -7px 8px 0;
+ margin: 8px 8px 8px 0;
font-size: 14px;
text-align: center;
color: currentColor;
@@ -983,6 +962,16 @@ input {
.top-nav-toggle .dropdown-icon {
margin-right: 0.5rem;
}
+.tanuki-logo .tanuki {
+ fill: #e24329;
+}
+.tanuki-logo .left-cheek,
+.tanuki-logo .right-cheek {
+ fill: #fc6d26;
+}
+.tanuki-logo .chin {
+ fill: #fca326;
+}
.context-header {
position: relative;
margin-right: 2px;
@@ -1499,8 +1488,16 @@ svg.s12 {
svg.s16 {
vertical-align: -3px;
}
+.header-content .header-search-new {
+ max-width: 600px;
+}
.header-search {
- width: 320px;
+ min-width: 320px;
+}
+@media (min-width: 768px) and (max-width: 1199.98px) {
+ .header-search {
+ min-width: 200px;
+ }
}
.header-search.is-not-active::after {
content: "/";
@@ -1521,6 +1518,7 @@ svg.s16 {
border-radius: 3px;
box-shadow: none;
white-space: pre-wrap;
+ box-sizing: border-box;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: keep-all;
@@ -1706,17 +1704,47 @@ svg.s16 {
.gl-display-none {
display: none;
}
+@media (min-width: 992px) {
+ .gl-lg-display-none\! {
+ display: none !important;
+ }
+}
+.gl-display-flex {
+ display: flex;
+}
+@media (min-width: 992px) {
+ .gl-lg-display-flex {
+ display: flex;
+ }
+}
@media (min-width: 576px) {
.gl-sm-display-block {
display: block;
}
}
+@media (min-width: 992px) {
+ .gl-lg-display-block {
+ display: block;
+ }
+}
+.gl-display-inline-block\! {
+ display: inline-block !important;
+}
+.gl-align-items-stretch {
+ align-items: stretch;
+}
+.gl-flex-grow-1 {
+ flex-grow: 1;
+}
.gl-relative {
position: relative;
}
.gl-absolute {
position: absolute;
}
+.gl-w-full {
+ width: 100%;
+}
.gl-px-3 {
padding-left: 0.5rem;
padding-right: 0.5rem;
@@ -1724,6 +1752,9 @@ svg.s16 {
.gl-pr-2 {
padding-right: 0.25rem;
}
+.gl-pt-0 {
+ padding-top: 0;
+}
.gl-ml-n2 {
margin-left: -0.25rem;
}
@@ -1734,6 +1765,12 @@ svg.s16 {
margin-left: 0 !important;
margin-right: 0 !important;
}
+.gl-text-right {
+ text-align: right;
+}
+.gl-white-space-nowrap {
+ white-space: nowrap;
+}
.gl-font-sm {
font-size: 0.75rem;
}
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 751ad26ca21..020ed9c040b 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -138,9 +138,8 @@ hr {
margin-right: -15px;
margin-left: -15px;
}
+.col-md-6,
.col-sm-12,
-.col-sm-7,
-.col-sm-5,
.col {
position: relative;
width: 100%;
@@ -159,14 +158,6 @@ hr {
order: 12;
}
@media (min-width: 576px) {
- .col-sm-5 {
- flex: 0 0 41.6666666667%;
- max-width: 41.6666666667%;
- }
- .col-sm-7 {
- flex: 0 0 58.3333333333%;
- max-width: 58.3333333333%;
- }
.col-sm-12 {
flex: 0 0 100%;
max-width: 100%;
@@ -178,6 +169,12 @@ hr {
order: 12;
}
}
+@media (min-width: 768px) {
+ .col-md-6 {
+ flex: 0 0 50%;
+ max-width: 50%;
+ }
+}
.form-control {
display: block;
width: 100%;
@@ -354,8 +351,7 @@ fieldset:disabled a.btn {
background-color: #fff;
}
.gl-button.gl-button.btn-default:active {
- box-shadow: inset 0 0 0 2px #5e5e5e, 0 0 0 1px rgba(255, 255, 255, 0.4),
- 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
background-color: #dbdbdb;
}
@@ -367,8 +363,7 @@ fieldset:disabled a.btn {
box-shadow: inset 0 0 0 1px #1068bf;
}
.gl-button.gl-button.btn-confirm:active {
- box-shadow: inset 0 0 0 2px #033464, 0 0 0 1px rgba(255, 255, 255, 0.4),
- 0 0 0 4px rgba(31, 117, 203, 0.48);
+ box-shadow: inset 0 0 0 1px #033464, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
background-color: #0b5cad;
}
@@ -519,6 +514,16 @@ label.label-bold {
.navbar-empty .brand-header-logo {
max-height: 100%;
}
+.tanuki-logo .tanuki {
+ fill: #e24329;
+}
+.tanuki-logo .left-cheek,
+.tanuki-logo .right-cheek {
+ fill: #fc6d26;
+}
+.tanuki-logo .chin {
+ fill: #fca326;
+}
input::-moz-placeholder {
color: #868686;
opacity: 1;
@@ -568,7 +573,6 @@ svg {
.login-page .omniauth-container {
box-shadow: 0 0 0 1px #dbdbdb;
border-radius: 0.25rem;
- padding: 15px;
}
.login-page .login-box .login-heading h3,
.login-page .omniauth-container .login-heading h3 {
diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss
index 550e3981401..6a9e96c3ac5 100644
--- a/app/assets/stylesheets/themes/_dark.scss
+++ b/app/assets/stylesheets/themes/_dark.scss
@@ -99,6 +99,7 @@ $white-normal: #333;
$white-dark: #444;
$border-color: #4f4f4f;
+$nav-active-bg: rgba(255, 255, 255, 0.08);
body.gl-dark {
--gray-10: #{$gray-10};
@@ -199,6 +200,7 @@ body.gl-dark {
--black: #{$black};
--svg-status-bg: #{$white};
+ --nav-active-bg: #{$nav-active-bg};
.gl-button.gl-button,
.gl-button.gl-button.btn-block {
diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss
index 83254fe1a52..dbb961fe71f 100644
--- a/app/assets/stylesheets/themes/dark_mode_overrides.scss
+++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss
@@ -71,10 +71,13 @@
body.gl-dark {
@include gitlab-theme($gray-900, $gray-400, $gray-500, $gray-900, $gray-900, $white);
- .logo-text svg {
- fill: var(--gl-text-color);
+ .terms {
+ .logo-text {
+ fill: var(--black);
+ }
}
+
.navbar-gitlab {
background-color: var(--gray-50);
box-shadow: 0 1px 0 0 var(--gray-100);
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index 07194e2b532..234010074aa 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -150,6 +150,7 @@
.header-search {
background-color: rgba($search-and-nav-links, 0.2) !important;
+ border-radius: $border-radius-default;
&:hover {
background-color: rgba($search-and-nav-links, 0.3) !important;
diff --git a/app/assets/stylesheets/themes/theme_light.scss b/app/assets/stylesheets/themes/theme_light.scss
index f2fdd499781..66b2b3c3437 100644
--- a/app/assets/stylesheets/themes/theme_light.scss
+++ b/app/assets/stylesheets/themes/theme_light.scss
@@ -15,8 +15,8 @@ body {
background-color: $gray-50;
box-shadow: 0 1px 0 0 $border-color;
- .logo-text svg {
- fill: $gray-900;
+ .logo-text {
+ fill: #171321;
}
.navbar-sub-nav,
@@ -48,6 +48,7 @@ body {
.header-search {
background-color: $white !important;
box-shadow: inset 0 0 0 1px $border-color !important;
+ border-radius: $border-radius-default;
&:hover {
background-color: $white !important;
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index d7a5e21e303..fd85ff894a7 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -366,3 +366,30 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
/* stylelint-disable property-no-vendor-prefix */
-webkit-backdrop-filter: blur(2px); // still required by Safari
}
+
+/*
+ * The below style will be moved to @gitlab/ui by
+ * https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1792
+ */
+.gl-text-purple-800 {
+ color: $purple-800;
+}
+
+.gl-bg-theme-indigo-800 {
+ background-color: $theme-indigo-800;
+}
+
+.gl-border-indigo-700 {
+ border-color: $theme-indigo-700;
+}
+
+.gl-border-gray-75 {
+ border-color: $gl-text-color-quaternary;
+}
+
+.gl-min-h-8 {
+ min-height: $gl-spacing-scale-8;
+}
+
+/* End gitlab-ui#1751 */
+