summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/auth_buttons/signin_with_google.pngbin0 -> 3983 bytes
-rw-r--r--app/assets/images/icon_image_comment.svg1
-rw-r--r--app/assets/images/icon_image_comment@2x.svg1
-rw-r--r--app/assets/images/icons.json2
-rw-r--r--app/assets/images/icons.svg2
-rw-r--r--app/assets/images/new_nav.pngbin14322 -> 0 bytes
-rw-r--r--app/assets/images/old_nav.pngbin25617 -> 0 bytes
-rw-r--r--app/assets/images/sprite.symbol.html3297
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js5
-rw-r--r--app/assets/javascripts/blob/notebook/index.js4
-rw-r--r--app/assets/javascripts/blob/pdf/index.js4
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js6
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js2
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js21
-rw-r--r--app/assets/javascripts/build.js2
-rw-r--r--app/assets/javascripts/build_artifacts.js24
-rw-r--r--app/assets/javascripts/clusters.js112
-rw-r--r--app/assets/javascripts/commit.js12
-rw-r--r--app/assets/javascripts/commit/file.js14
-rw-r--r--app/assets/javascripts/commit/image_file.js13
-rw-r--r--app/assets/javascripts/copy_as_gfm.js61
-rw-r--r--app/assets/javascripts/create_label.js29
-rw-r--r--app/assets/javascripts/cycle_analytics/components/banner.vue55
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.vue4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_component.vue4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue34
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue34
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js8
-rw-r--r--app/assets/javascripts/diff.js23
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js9
-rw-r--r--app/assets/javascripts/dispatcher.js15
-rw-r--r--app/assets/javascripts/environments/components/environment.vue2
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue2
-rw-r--r--app/assets/javascripts/files_comment_button.js6
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_emoji.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js2
-rw-r--r--app/assets/javascripts/gl_dropdown.js7
-rw-r--r--app/assets/javascripts/image_diff/helpers/badge_helper.js38
-rw-r--r--app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js58
-rw-r--r--app/assets/javascripts/image_diff/helpers/dom_helper.js44
-rw-r--r--app/assets/javascripts/image_diff/helpers/index.js25
-rw-r--r--app/assets/javascripts/image_diff/helpers/utils_helper.js95
-rw-r--r--app/assets/javascripts/image_diff/image_badge.js23
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js143
-rw-r--r--app/assets/javascripts/image_diff/init_discussion_tab.js12
-rw-r--r--app/assets/javascripts/image_diff/replaced_image_diff.js92
-rw-r--r--app/assets/javascripts/image_diff/view_types.js9
-rw-r--r--app/assets/javascripts/issuable_context.js5
-rw-r--r--app/assets/javascripts/labels_select.js3
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js27
-rw-r--r--app/assets/javascripts/lib/utils/csrf.js4
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js4
-rw-r--r--app/assets/javascripts/lib/utils/image_utility.js5
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js20
-rw-r--r--app/assets/javascripts/line_highlighter.js285
-rw-r--r--app/assets/javascripts/locale/index.js27
-rw-r--r--app/assets/javascripts/locale/sprintf.js26
-rw-r--r--app/assets/javascripts/main.js14
-rw-r--r--app/assets/javascripts/merge_request.js15
-rw-r--r--app/assets/javascripts/merge_request_tabs.js6
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue8
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue42
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue59
-rw-r--r--app/assets/javascripts/monitoring/components/graph/deployment.vue14
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/graph/legend.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/graph/path.vue (renamed from app/assets/javascripts/monitoring/components/graph_path.vue)0
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js22
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js5
-rw-r--r--app/assets/javascripts/monitoring/stores/monitoring_store.js2
-rw-r--r--app/assets/javascripts/monitoring/utils/date_time_formatters.js1
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js4
-rw-r--r--app/assets/javascripts/notebook/cells/code.vue30
-rw-r--r--app/assets/javascripts/notebook/cells/code/index.vue28
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue14
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue14
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue16
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue18
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue16
-rw-r--r--app/assets/javascripts/notebook/index.vue22
-rw-r--r--app/assets/javascripts/notes.js128
-rw-r--r--app/assets/javascripts/notes/components/issue_comment_form.vue31
-rw-r--r--app/assets/javascripts/notes/components/issue_discussion_locked_widget.vue19
-rw-r--r--app/assets/javascripts/notes/components/issue_note.vue2
-rw-r--r--app/assets/javascripts/notes/components/issue_note_form.vue20
-rw-r--r--app/assets/javascripts/notes/mixins/issuable_state.js15
-rw-r--r--app/assets/javascripts/pdf/index.vue20
-rw-r--r--app/assets/javascripts/pdf/page/index.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue11
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js2
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue146
-rw-r--r--app/assets/javascripts/profile/account/index.js21
-rw-r--r--app/assets/javascripts/profile/gl_crop.js3
-rw-r--r--app/assets/javascripts/project_fork.js18
-rw-r--r--app/assets/javascripts/projects_dropdown/service/projects_service.js2
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js51
-rw-r--r--app/assets/javascripts/registry/components/app.vue62
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue131
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue137
-rw-r--r--app/assets/javascripts/registry/constants.js15
-rw-r--r--app/assets/javascripts/registry/index.js25
-rw-r--r--app/assets/javascripts/registry/stores/actions.js39
-rw-r--r--app/assets/javascripts/registry/stores/getters.js2
-rw-r--r--app/assets/javascripts/registry/stores/index.js39
-rw-r--r--app/assets/javascripts/registry/stores/mutation_types.js7
-rw-r--r--app/assets/javascripts/registry/stores/mutations.js54
-rw-r--r--app/assets/javascripts/repo/components/repo.vue2
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue6
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue10
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue16
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue2
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue25
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue62
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js18
-rw-r--r--app/assets/javascripts/repo/index.js4
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js10
-rw-r--r--app/assets/javascripts/right_sidebar.js42
-rw-r--r--app/assets/javascripts/search_autocomplete.js1
-rw-r--r--app/assets/javascripts/shortcuts.js15
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue14
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form.vue9
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form.vue61
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue50
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue120
-rw-r--r--app/assets/javascripts/sidebar/lib/sidebar_move_issue.js4
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js82
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js2
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js1
-rw-r--r--app/assets/javascripts/single_file_diff.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js48
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js48
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js27
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/confidential_issue_warning.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue55
-rw-r--r--app/assets/javascripts/vue_shared/components/popup_dialog.vue6
-rw-r--r--app/assets/javascripts/vue_shared/mixins/issuable.js9
-rw-r--r--app/assets/javascripts/vue_shared/translate.js2
-rw-r--r--app/assets/stylesheets/framework.scss4
-rw-r--r--app/assets/stylesheets/framework/animations.scss3
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss10
-rw-r--r--app/assets/stylesheets/framework/buttons.scss26
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss15
-rw-r--r--app/assets/stylesheets/framework/gfm.scss11
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss14
-rw-r--r--app/assets/stylesheets/framework/header.scss598
-rw-r--r--app/assets/stylesheets/framework/images.scss3
-rw-r--r--app/assets/stylesheets/framework/layout.scss4
-rw-r--r--app/assets/stylesheets/framework/lists.scss4
-rw-r--r--app/assets/stylesheets/framework/modal.scss13
-rw-r--r--app/assets/stylesheets/framework/new-nav.scss403
-rw-r--r--app/assets/stylesheets/framework/new-sidebar.scss57
-rw-r--r--app/assets/stylesheets/framework/secondary-navigation-elements.scss (renamed from app/assets/stylesheets/framework/nav.scss)335
-rw-r--r--app/assets/stylesheets/framework/selects.scss145
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss6
-rw-r--r--app/assets/stylesheets/framework/timeline.scss6
-rw-r--r--app/assets/stylesheets/framework/tooltips.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss27
-rw-r--r--app/assets/stylesheets/pages/boards.scss27
-rw-r--r--app/assets/stylesheets/pages/builds.scss8
-rw-r--r--app/assets/stylesheets/pages/clusters.scss9
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss185
-rw-r--r--app/assets/stylesheets/pages/environments.scss19
-rw-r--r--app/assets/stylesheets/pages/issuable.scss34
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss43
-rw-r--r--app/assets/stylesheets/pages/notes.scss25
-rw-r--r--app/assets/stylesheets/pages/profile.scss13
-rw-r--r--app/assets/stylesheets/pages/projects.scss90
-rw-r--r--app/assets/stylesheets/pages/repo.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss11
-rw-r--r--app/assets/stylesheets/pages/settings_ci_cd.scss4
-rw-r--r--app/assets/stylesheets/pages/tree.scss8
-rw-r--r--app/controllers/admin/application_controller.rb14
-rw-r--r--app/controllers/admin/applications_controller.rb10
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/application_controller.rb22
-rw-r--r--app/controllers/boards/issues_controller.rb2
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb2
-rw-r--r--app/controllers/concerns/issuable_collections.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb13
-rw-r--r--app/controllers/confirmations_controller.rb14
-rw-r--r--app/controllers/dashboard/todos_controller.rb30
-rw-r--r--app/controllers/google_api/authorizations_controller.rb29
-rw-r--r--app/controllers/oauth/applications_controller.rb10
-rw-r--r--app/controllers/profiles/avatars_controller.rb2
-rw-r--r--app/controllers/profiles/emails_controller.rb29
-rw-r--r--app/controllers/profiles/gpg_keys_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb8
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/profiles/passwords_controller.rb6
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb2
-rw-r--r--app/controllers/profiles/preferences_controller.rb2
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb6
-rw-r--r--app/controllers/profiles_controller.rb10
-rw-r--r--app/controllers/projects/artifacts_controller.rb18
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/clusters_controller.rb136
-rw-r--r--app/controllers/projects/git_http_client_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb11
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/lfs_api_controller.rb18
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb7
-rw-r--r--app/controllers/projects/notes_controller.rb9
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb21
-rw-r--r--app/controllers/projects/registry/tags_controller.rb27
-rw-r--r--app/controllers/projects/tree_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb16
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb33
-rw-r--r--app/controllers/sessions_controller.rb55
-rw-r--r--app/finders/concerns/custom_attributes_filter.rb20
-rw-r--r--app/finders/issuable_finder.rb22
-rw-r--r--app/finders/merge_request_target_project_finder.rb18
-rw-r--r--app/finders/users_finder.rb2
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/helpers/breadcrumbs_helper.rb8
-rw-r--r--app/helpers/diff_helper.rb16
-rw-r--r--app/helpers/events_helper.rb20
-rw-r--r--app/helpers/icons_helper.rb4
-rw-r--r--app/helpers/issuables_helper.rb21
-rw-r--r--app/helpers/merge_requests_helper.rb3
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/numbers_helper.rb11
-rw-r--r--app/helpers/page_layout_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb8
-rw-r--r--app/helpers/system_note_helper.rb4
-rw-r--r--app/mailers/emails/profile.rb6
-rw-r--r--app/models/ci/artifact_blob.rb26
-rw-r--r--app/models/ci/build.rb9
-rw-r--r--app/models/ci/build_trace_section.rb11
-rw-r--r--app/models/ci/build_trace_section_name.rb11
-rw-r--r--app/models/ci/pipeline.rb22
-rw-r--r--app/models/commit.rb11
-rw-r--r--app/models/concerns/cache_markdown_field.rb14
-rw-r--r--app/models/concerns/discussion_on_diff.rb4
-rw-r--r--app/models/concerns/has_status.rb1
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/concerns/repository_mirroring.rb25
-rw-r--r--app/models/concerns/routable.rb6
-rw-r--r--app/models/concerns/token_authenticatable.rb4
-rw-r--r--app/models/diff_discussion.rb2
-rw-r--r--app/models/diff_note.rb14
-rw-r--r--app/models/discussion.rb4
-rw-r--r--app/models/email.rb12
-rw-r--r--app/models/fork_network.rb15
-rw-r--r--app/models/fork_network_member.rb7
-rw-r--r--app/models/gcp/cluster.rb113
-rw-r--r--app/models/gpg_key.rb22
-rw-r--r--app/models/gpg_key_subkey.rb22
-rw-r--r--app/models/gpg_signature.rb33
-rw-r--r--app/models/key.rb1
-rw-r--r--app/models/legacy_diff_discussion.rb8
-rw-r--r--app/models/merge_request.rb42
-rw-r--r--app/models/merge_request_diff.rb1
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/note.rb16
-rw-r--r--app/models/personal_access_token.rb6
-rw-r--r--app/models/project.rb132
-rw-r--r--app/models/project_services/chat_message/base_message.rb10
-rw-r--r--app/models/project_services/chat_message/issue_message.rb6
-rw-r--r--app/models/project_services/chat_message/merge_message.rb4
-rw-r--r--app/models/project_services/chat_message/note_message.rb4
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb6
-rw-r--r--app/models/project_services/chat_message/push_message.rb8
-rw-r--r--app/models/project_services/chat_message/wiki_page_message.rb4
-rw-r--r--app/models/project_wiki.rb59
-rw-r--r--app/models/repository.rb64
-rw-r--r--app/models/storage/hashed_project.rb1
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb89
-rw-r--r--app/models/user_custom_attribute.rb6
-rw-r--r--app/models/wiki_page.rb10
-rw-r--r--app/policies/gcp/cluster_policy.rb12
-rw-r--r--app/policies/global_policy.rb11
-rw-r--r--app/policies/issuable_policy.rb12
-rw-r--r--app/policies/namespace_policy.rb4
-rw-r--r--app/policies/note_policy.rb2
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/presenters/ci/pipeline_presenter.rb11
-rw-r--r--app/presenters/gcp/cluster_presenter.rb9
-rw-r--r--app/presenters/merge_request_presenter.rb2
-rw-r--r--app/serializers/cluster_entity.rb6
-rw-r--r--app/serializers/cluster_serializer.rb7
-rw-r--r--app/serializers/commit_entity.rb2
-rw-r--r--app/serializers/container_repositories_serializer.rb3
-rw-r--r--app/serializers/container_repository_entity.rb25
-rw-r--r--app/serializers/container_tag_entity.rb23
-rw-r--r--app/serializers/container_tags_serializer.rb17
-rw-r--r--app/serializers/issue_entity.rb3
-rw-r--r--app/serializers/merge_request_entity.rb8
-rw-r--r--app/serializers/pipeline_entity.rb8
-rw-r--r--app/services/ci/create_cluster_service.rb15
-rw-r--r--app/services/ci/create_pipeline_service.rb150
-rw-r--r--app/services/ci/extract_sections_from_build_trace_service.rb30
-rw-r--r--app/services/ci/fetch_gcp_operation_service.rb17
-rw-r--r--app/services/ci/fetch_kubernetes_token_service.rb72
-rw-r--r--app/services/ci/finalize_cluster_creation_service.rb33
-rw-r--r--app/services/ci/integrate_cluster_service.rb26
-rw-r--r--app/services/ci/provision_cluster_service.rb36
-rw-r--r--app/services/ci/update_cluster_service.rb22
-rw-r--r--app/services/emails/base_service.rb6
-rw-r--r--app/services/emails/confirm_service.rb7
-rw-r--r--app/services/emails/create_service.rb4
-rw-r--r--app/services/emails/destroy_service.rb6
-rw-r--r--app/services/issuable_base_service.rb12
-rw-r--r--app/services/keys/last_used_service.rb2
-rw-r--r--app/services/merge_requests/ff_merge_service.rb24
-rw-r--r--app/services/merge_requests/merge_service.rb24
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/notification_service.rb7
-rw-r--r--app/services/projects/destroy_service.rb7
-rw-r--r--app/services/projects/fork_service.rb20
-rw-r--r--app/services/projects/hashed_storage_migration_service.rb68
-rw-r--r--app/services/projects/unlink_fork_service.rb1
-rw-r--r--app/services/system_note_service.rb7
-rw-r--r--app/services/tags/create_service.rb2
-rw-r--r--app/services/users/activity_service.rb2
-rw-r--r--app/services/users/update_service.rb19
-rw-r--r--app/views/admin/jobs/index.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_account.html.haml16
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_account.text.erb14
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_secondary.html.haml8
-rw-r--r--app/views/devise/mailer/_confirmation_instructions_secondary.text.erb7
-rw-r--r--app/views/devise/mailer/confirmation_instructions.html.haml16
-rw-r--r--app/views/devise/mailer/confirmation_instructions.text.erb10
-rw-r--r--app/views/discussions/_diff_discussion.html.haml16
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml31
-rw-r--r--app/views/discussions/_notes.html.haml19
-rw-r--r--app/views/groups/milestones/_header_title.html.haml3
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml22
-rw-r--r--app/views/layouts/header/_new_dropdown.haml4
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml6
-rw-r--r--app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml3
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml8
-rw-r--r--app/views/notify/new_email_email.html.haml10
-rw-r--r--app/views/notify/new_email_email.text.erb7
-rw-r--r--app/views/profiles/accounts/show.html.haml20
-rw-r--r--app/views/profiles/emails/index.html.haml16
-rw-r--r--app/views/profiles/gpg_keys/_key.html.haml9
-rw-r--r--app/views/projects/_home_panel.html.haml14
-rw-r--r--app/views/projects/_md_preview.html.haml7
-rw-r--r--app/views/projects/_merge_request_fast_forward_settings.html.haml13
-rw-r--r--app/views/projects/_merge_request_rebase_settings.html.haml13
-rw-r--r--app/views/projects/_merge_request_settings.html.haml15
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml15
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml31
-rw-r--r--app/views/projects/buttons/_fork.html.haml9
-rw-r--r--app/views/projects/clusters/_form.html.haml37
-rw-r--r--app/views/projects/clusters/_header.html.haml14
-rw-r--r--app/views/projects/clusters/_sidebar.html.haml7
-rw-r--r--app/views/projects/clusters/login.html.haml16
-rw-r--r--app/views/projects/clusters/new.html.haml9
-rw-r--r--app/views/projects/clusters/show.html.haml70
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml15
-rw-r--r--app/views/projects/diffs/_image_diff_frame.html.haml5
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml16
-rw-r--r--app/views/projects/diffs/_replaced_image_diff.html.haml61
-rw-r--r--app/views/projects/diffs/_single_image_diff.html.haml16
-rw-r--r--app/views/projects/diffs/viewers/_image.html.haml70
-rw-r--r--app/views/projects/forks/new.html.haml74
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml4
-rw-r--r--app/views/projects/issues/edit.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/jobs/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/registry/repositories/_image.html.haml32
-rw-r--r--app/views/projects/registry/repositories/index.html.haml93
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml7
-rw-r--r--app/views/projects/tree/_old_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml4
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml22
-rw-r--r--app/views/projects/wikis/_main_links.html.haml6
-rw-r--r--app/views/projects/wikis/_new.html.haml11
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml4
-rw-r--r--app/views/projects/wikis/edit.html.haml19
-rw-r--r--app/views/projects/wikis/empty.html.haml6
-rw-r--r--app/views/projects/wikis/git_access.html.haml14
-rw-r--r--app/views/projects/wikis/history.html.haml18
-rw-r--r--app/views/projects/wikis/pages.html.haml8
-rw-r--r--app/views/projects/wikis/show.html.haml14
-rw-r--r--app/views/shared/_auto_devops_callout.html.haml10
-rw-r--r--app/views/shared/_email_with_badge.html.haml (renamed from app/views/profiles/gpg_keys/_email_with_badge.html.haml)4
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml4
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml6
-rw-r--r--app/views/shared/_target_switcher.html.haml16
-rw-r--r--app/views/shared/boards/_show.html.haml8
-rw-r--r--app/views/shared/builds/_tabs.html.haml8
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml4
-rw-r--r--app/views/shared/issuable/_close_reopen_report_toggle.html.haml6
-rw-r--r--app/views/shared/issuable/_participants.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/notes/_comment_button.html.haml2
-rw-r--r--app/views/shared/notes/_form.html.haml35
-rw-r--r--app/views/shared/notes/_note.html.haml15
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml14
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/build_finished_worker.rb1
-rw-r--r--app/workers/build_trace_sections_worker.rb8
-rw-r--r--app/workers/cluster_provision_worker.rb10
-rw-r--r--app/workers/concerns/cluster_queue.rb10
-rw-r--r--app/workers/project_migrate_hashed_storage_worker.rb11
-rw-r--r--app/workers/storage_migrator_worker.rb30
-rw-r--r--app/workers/wait_for_cluster_creation_worker.rb27
440 files changed, 6721 insertions, 6207 deletions
diff --git a/app/assets/images/auth_buttons/signin_with_google.png b/app/assets/images/auth_buttons/signin_with_google.png
new file mode 100644
index 00000000000..b1327b4f7b4
--- /dev/null
+++ b/app/assets/images/auth_buttons/signin_with_google.png
Binary files differ
diff --git a/app/assets/images/icon_image_comment.svg b/app/assets/images/icon_image_comment.svg
new file mode 100644
index 00000000000..cf6cb972940
--- /dev/null
+++ b/app/assets/images/icon_image_comment.svg
@@ -0,0 +1 @@
+<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
diff --git a/app/assets/images/icon_image_comment@2x.svg b/app/assets/images/icon_image_comment@2x.svg
new file mode 100644
index 00000000000..83be91d3705
--- /dev/null
+++ b/app/assets/images/icon_image_comment@2x.svg
@@ -0,0 +1 @@
+<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
diff --git a/app/assets/images/icons.json b/app/assets/images/icons.json
index e5da75faf38..6b8f85e37fd 100644
--- a/app/assets/images/icons.json
+++ b/app/assets/images/icons.json
@@ -1 +1 @@
-{"iconCount":134,"icons":["abuse","account","admin","angle-double-left","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close-m","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} \ No newline at end of file
+{"iconCount":135,"spriteSize":58718,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} \ No newline at end of file
diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
index 5c3a9962bd3..30cb2109ec2 100644
--- a/app/assets/images/icons.svg
+++ b/app/assets/images/icons.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 16 16" id="abuse" xmlns="http://www.w3.org/2000/svg"><path d="M11.408.328l4.029 3.222A1.5 1.5 0 0 1 16 4.72v6.555a1.5 1.5 0 0 1-.563 1.171l-4.026 3.224a1.5 1.5 0 0 1-.937.329H5.529a1.5 1.5 0 0 1-.937-.328L.563 12.45A1.5 1.5 0 0 1 0 11.28V4.724a1.5 1.5 0 0 1 .563-1.171L4.589.329A1.5 1.5 0 0 1 5.526 0h4.945c.34 0 .67.116.937.328zM10.296 2H5.702L2 4.964v6.074L5.704 14h4.594L14 11.036V4.962L10.296 2zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="account" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.195 9.965l-.568-.875a.25.25 0 0 1 .015-.294l.405-.5a.25.25 0 0 1 .283-.075l.938.36c.257-.183.543-.325.851-.42l.322-.988A.25.25 0 0 1 11.679 7h.642a.25.25 0 0 1 .238.173l.322.988c.308.095.594.237.851.42l.938-.36a.25.25 0 0 1 .283.076l.405.5a.25.25 0 0 1 .015.293l-.568.875c.113.297.18.616.193.95l.898.54a.25.25 0 0 1 .115.27l-.144.626a.25.25 0 0 1-.222.193l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.281a.25.25 0 0 1-.29-.05l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.904a.25.25 0 0 1-.289.051l-.577-.281a.25.25 0 0 1-.138-.26l.165-1.18a3.015 3.015 0 0 1-.512-.607l-1.115-.098a.25.25 0 0 1-.222-.193l-.144-.626a.25.25 0 0 1 .115-.27l.898-.54c.013-.334.08-.653.193-.95zM6.789 8.023A12.845 12.845 0 0 0 6 8c-5.036 0-6 2.74-6 4.48C0 14.22.076 15 6 15c.553 0 1.055-.006 1.51-.02A5.977 5.977 0 0 1 6 11c0-1.083.287-2.1.79-2.977zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM12 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="admin" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.162 2.5a3.5 3.5 0 0 1-3.163 5.479L6.08 14.766a1.5 1.5 0 0 1-2.598-1.5L7.4 6.479A3.5 3.5 0 0 1 10.564 1L8.9 3.88l2.599 1.5 1.663-2.88zm-8.63 11.949a.5.5 0 1 0 .5-.866.5.5 0 0 0-.5.866z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.414 7.95l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 1 0 1.414-1.415L10.414 7.95zm-7 0l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 0 0 1.414-1.415L3.414 7.95z"/></symbol><symbol viewBox="0 0 16 16" id="angle-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></symbol><symbol viewBox="0 0 16 16" id="angle-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.757 8l4.95-4.95a1 1 0 1 0-1.414-1.414L3.636 7.293a.997.997 0 0 0 0 1.414l5.657 5.657a1 1 0 0 0 1.414-1.414L5.757 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.243 8l-4.95-4.95a1 1 0 0 1 1.414-1.414l5.657 5.657a.997.997 0 0 1 0 1.414l-5.657 5.657a1 1 0 0 1-1.414-1.414L10.243 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 6.757l-4.95 4.95a1 1 0 1 1-1.414-1.414l5.657-5.657a.997.997 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L8 6.757z"/></symbol><symbol viewBox="0 0 16 16" id="appearance" xmlns="http://www.w3.org/2000/svg"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858a23.85 23.85 0 0 1-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023a9.7 9.7 0 0 0 .478-.035c-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="applications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="approval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.536 10.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 1 1 9.12 9.243l1.415 1.414zM7.632 8.109A2 2 0 0 0 7 11.364l2.121 2.121a1.996 1.996 0 0 0 2.807.021C11.686 14.554 10.627 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.6 0 1.142.038 1.632.109zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></symbol><symbol viewBox="0 0 16 16" id="assignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 5V4a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V7h-1a1 1 0 0 1 0-2h1zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="bold" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 15V1a1 1 0 0 1 1-1h4.604c.93 0 1.762.088 2.495.264.733.176 1.353.445 1.863.807.509.363.897.82 1.164 1.369.268.549.401 1.197.401 1.945 0 .366-.045.718-.137 1.055-.091.337-.23.652-.417.945a3.453 3.453 0 0 1-.71.796 3.645 3.645 0 0 1-1.021.588c.469.117.87.295 1.203.533.333.238.608.515.824.83.216.315.374.657.473 1.027.099.37.148.75.148 1.138 0 1.553-.5 2.725-1.5 3.516-1 .791-2.423 1.187-4.27 1.187H3a1 1 0 0 1-1-1zm3.297-5.967v4.319H8.12c.425 0 .791-.053 1.099-.16.307-.106.564-.252.769-.44.205-.186.357-.406.456-.659.099-.252.148-.529.148-.83a3.04 3.04 0 0 0-.131-.928 1.78 1.78 0 0 0-.413-.703 1.8 1.8 0 0 0-.73-.445c-.3-.103-.66-.154-1.077-.154H5.297zm0-2.33h2.44c.842-.014 1.468-.192 1.878-.533.41-.34.616-.826.616-1.456 0-.725-.21-1.247-.632-1.566-.421-.318-1.086-.478-1.995-.478H5.297v4.033z"/></symbol><symbol viewBox="0 0 16 16" id="book" xmlns="http://www.w3.org/2000/svg"><path d="M7 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 7 6.191V2zM5 0h6a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="branch" xmlns="http://www.w3.org/2000/svg"><path d="M6 11.978v.29a2 2 0 1 1-2 0V3.732a2 2 0 1 1 2 0v3.849c.592-.491 1.31-.854 2.15-1.081 1.308-.353 1.875-.882 1.893-1.743a2 2 0 1 1 2.002-.051C12.053 6.54 10.857 7.84 8.67 8.43 7.056 8.867 6.195 9.98 6 11.978zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm6 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 15a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="calendar" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 2h2a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2h2V1a1 1 0 1 1 2 0v1h4V1a1 1 0 1 1 2 0v1zM0 4h16v9a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4zm2 2.5V13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5zM5 8h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="cancel" xmlns="http://www.w3.org/2000/svg"><path d="M3.11 4.523a6 6 0 0 0 8.367 8.367L3.109 4.524zM4.522 3.11l8.368 8.368A6 6 0 0 0 4.524 3.11zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.078 8.2l3.535-3.536a2 2 0 0 1 2.828 2.828l-4.949 4.95c-.39.39-.902.586-1.414.586a1.994 1.994 0 0 1-1.414-.586l-4.95-4.95a2 2 0 1 1 2.828-2.828l3.536 3.535z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.977 7.998l3.535-3.535a2 2 0 1 0-2.828-2.828l-4.95 4.949c-.39.39-.586.902-.586 1.414 0 .512.196 1.024.586 1.414l4.95 4.95a2 2 0 1 0 2.828-2.828L7.977 7.998z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.22 7.998L4.683 4.463a2 2 0 0 1 2.828-2.828l4.95 4.949c.39.39.586.902.586 1.414a1.99 1.99 0 0 1-.586 1.414l-4.95 4.95a2 2 0 0 1-2.828-2.828l3.535-3.536z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.778 8.957l3.535 3.535a2 2 0 1 0 2.828-2.828l-4.949-4.95a1.994 1.994 0 0 0-1.414-.586c-.512 0-1.024.196-1.414.586l-4.95 4.95a2 2 0 1 0 2.828 2.828l3.536-3.535z"/></symbol><symbol viewBox="0 0 16 16" id="clock" xmlns="http://www.w3.org/2000/svg"><path d="M9 7h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V5a1 1 0 1 1 2 0v2zm-1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="code" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.871 8.243a.997.997 0 0 0-.293-.707L12.75 4.707a1 1 0 0 0-1.414 1.414l2.12 2.122-2.12 2.121a1 1 0 0 0 1.414 1.414l2.828-2.828a.997.997 0 0 0 .293-.707zm-13.243 0L4.75 6.12a1 1 0 1 0-1.414-1.414L.507 7.536a.997.997 0 0 0 0 1.414l2.829 2.828a1 1 0 1 0 1.414-1.414L2.628 8.243zm6.407-4.107a1 1 0 0 1 .707 1.225L8.19 11.157a1 1 0 1 1-1.931-.518L7.81 4.843a1 1 0 0 1 1.224-.707z"/></symbol><symbol viewBox="0 0 16 16" id="comment" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comment-dots" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586zM5 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="comment-next" xmlns="http://www.w3.org/2000/svg"><path d="M8 5V4a.5.5 0 0 1 .8-.4l2.667 2a.5.5 0 0 1 0 .8L8.8 8.4A.5.5 0 0 1 8 8V7H6a1 1 0 1 1 0-2h2zM1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comments" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.75 10L0 13V3a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3.75zM13 5h1a2 2 0 0 1 2 2v8l-2.667-2H8a2 2 0 0 1-2-2h4a3 3 0 0 0 3-3V5z"/></symbol><symbol viewBox="0 0 16 16" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.876-1.008a4.002 4.002 0 0 1-7.752 0A1.01 1.01 0 0 1 4 9H1a1 1 0 1 1 0-2h3c.042 0 .083.003.124.008a4.002 4.002 0 0 1 7.752 0A1.01 1.01 0 0 1 12 7h3a1 1 0 0 1 0 2h-3a1.01 1.01 0 0 1-.124-.008z"/></symbol><symbol viewBox="0 0 16 16" id="credit-card" xmlns="http://www.w3.org/2000/svg"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="disk" xmlns="http://www.w3.org/2000/svg"><path d="M16 11.764V3a3 3 0 0 0-3-3H3a3 3 0 0 0-3 3v8.764A2.989 2.989 0 0 1 2 11V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v8c.768 0 1.47.289 2 .764zM2 12h12a2 2 0 1 1 0 4H2a2 2 0 1 1 0-4zm10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="doc_code" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zm1.036 7.607a.498.498 0 0 1-.147.354l-1.414 1.414a.5.5 0 0 1-.707-.707l1.06-1.06-1.06-1.061a.5.5 0 0 1 .707-.707l1.414 1.414a.498.498 0 0 1 .147.353zm-4.822 0l1.06 1.061a.5.5 0 0 1-.706.707l-1.414-1.414a.498.498 0 0 1 0-.707l1.414-1.414a.5.5 0 1 1 .707.707l-1.06 1.06zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="doc_image" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM7.333 9.667l1.313-1.313a.5.5 0 0 1 .708 0L12 11H4l2.188-1.75a.5.5 0 0 1 .624 0l.521.417zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 8a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4 11h8v.7a.3.3 0 0 1-.3.3H4.3a.3.3 0 0 1-.3-.3V11z"/></symbol><symbol viewBox="0 0 16 16" id="doc_text" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="download" xmlns="http://www.w3.org/2000/svg"><path d="M9 12h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0l-2-2.667A.5.5 0 0 1 6 12h1V8a1 1 0 1 1 2 0v4zM4 9a1 1 0 1 1 0 2 4 4 0 0 1-1.971-7.481 4 4 0 0 1 6.633-2.505 3.999 3.999 0 0 1 3.82 2.014A4 4 0 0 1 12 11a1 1 0 0 1 0-2 2 2 0 1 0 0-4h-1a2 2 0 0 0-3.112-1.662A2 2 0 1 0 4.268 5H4a2 2 0 1 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-3a1 1 0 0 1-1-1V6H8.527A.527.527 0 0 0 8 6.527V13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3zm-4-7H8.527c-.18 0-.355.013-.527.04V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2v2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3zM8.527 4h2.323a.5.5 0 0 1 .35.143l4.65 4.551a.5.5 0 0 1 .15.357V13a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6.527A2.527 2.527 0 0 1 8.527 4z"/></symbol><symbol viewBox="0 0 16 16" id="earth" xmlns="http://www.w3.org/2000/svg"><path d="M8.7 2.04l-.082.177c.283.223.422.413.417.571-.008.237-.311.057-.444.274-.133.218.038.542-.112.637-.15.096-.398-.386-.479-.46-.054-.049-.166-.257-.336-.625l-.216-.225a.844.844 0 0 0-.418-.035c-.177.038-.075.1-.035.132.04.032.32.037.452.2.132.164.03.224-.05.298-.054.05-.157.062-.31.035H5.952l-.402.398.03.325.229.455.324-.463c.008-.206.058-.342.15-.41.14-.1.342-.15.534-.085.191.066-.057.218.011.271.068.053.204-.098.313-.02.11.08.07.155.104.322.036.167.254.114.398.328.144.215.19.29.147.483-.043.195-.168.26-.305.232-.138-.028-.107-.246-.275-.348-.168-.102-.266-.114-.386-.054-.12.06-.016.129.023.235.04.106.274.321.224.43-.05.107-.108.116-.42 0-.21-.077-.414-.007-.615.212l-.76.722c-.153.715-.3 1.13-.44 1.243-.211.17-.177-.483-.483-.656-.306-.174-.494-.047-.8-.07-.307-.023-.42.65-.38.873a.434.434 0 0 0 .221.321c.236-.141.39-.184.465-.128.11.084-.144.267-.074.425.07.158.314.069.386.283.073.213.084.48-.05.706-.135.227-.275.178-.4.053-.127-.126-.033-.375-.255-.704-.223-.329-.381-.337-.63-.787-.158-.287-.35-.743-.575-1.366a6 6 0 0 0 3.21 7.198l.001-.075c0-.577-.004-.944-.012-1.102-.011-.236-.95-.945-1.104-1.2-.154-.256-.34-.595-.355-.746-.016-.151.185-.232.344-.325.16-.093-.11-.367.028-.626.137-.258.395-.438.496-.356.101.081.058.228.267.333.209.104.077-.213.456-.178.38.035.143.201.252.216.11.016.113-.127.299-.143.186-.015.282.445.471.622.19.178.452.008.611.043.159.034.267.09.402.255.136.166-.03.352.073.557.103.205 1.07.22 1.433.255.364.034.371.011.371.324s-.166.314-.453.507c-.286.193-.166.462-.38.762-.212.3-.316.062-.622.14-.306.077-.413.382-.452.568-.039.186-.386.094-.877.232-.29.082-.429.144-.569.204a6.002 6.002 0 0 0 7.682-4.3c-.094-.384-.18-.63-.258-.74-.213-.297-.36.21-.924.49-.564.278-.57-.288-.81-.49-.16-.133-.212-.44-.158-.92-.005-.478.02-.828.077-1.049.057-.221.126-.543.207-.965.351-.373.606-.572.764-.595.237-.034.336.374.658.3a.315.315 0 0 0 .035-.01 5.993 5.993 0 0 0-.475-.824l-.309-.043a.646.646 0 0 0-.332-.117c-.205-.02-.025.128-.089.24-.064.112-.235.724-.437.685-.201-.039-.204-.374-.17-.668.036-.294-.077-.35-.2-.412-.124-.062-.325-.213-.556-.295-.232-.082-.123-.175-.093-.274.03-.1.208-.015.193-.058-.014-.044-.313-.135-.266-.167.03-.02.2-.02.506.003l.216-.012.293-.163a.58.58 0 0 0-.376-.22c-.233-.036-.513-.034-.73-.142-.205-.103-.458-.36-.643-.638A5.965 5.965 0 0 0 8.7 2.04zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="eye" xmlns="http://www.w3.org/2000/svg"><path d="M8 14C4.816 14 2.253 12.284.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2s5.747 1.716 7.607 5.019a2 2 0 0 1 0 1.962C13.747 12.284 11.184 14 8 14zm0-2c2.41 0 4.338-1.29 5.864-4C12.338 5.29 10.411 4 8 4 5.59 4 3.662 5.29 2.136 8 3.662 10.71 5.589 12 8 12zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm1-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M13.618 2.62L1.62 14.619a1 1 0 0 1-.985-1.668l1.525-1.526C1.516 10.742.926 9.927.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2c1.074 0 2.076.195 3.006.58l.944-.944a1 1 0 0 1 1.668.985zM8.068 11a3 3 0 0 0 2.931-2.932l-2.931 2.931zm-3.02-2.462a3 3 0 0 1 3.49-3.49l.884-.884A6.044 6.044 0 0 0 8 4C5.59 4 3.662 5.29 2.136 8c.445.79.924 1.46 1.439 2.011l1.473-1.473zm.421 5.06l1.658-1.658c.283.04.575.06.873.06 2.41 0 4.338-1.29 5.864-4a11.023 11.023 0 0 0-1.133-1.664l1.418-1.418a12.799 12.799 0 0 1 1.458 2.1 2 2 0 0 1 0 1.963C13.747 12.284 11.184 14 8 14a7.883 7.883 0 0 1-2.53-.402z"/></symbol><symbol viewBox="0 0 16 16" id="file-additions" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3z"/></symbol><symbol viewBox="0 0 16 16" id="file-deletion" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm2 6h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="file-modified" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm5 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></symbol><symbol viewBox="0 0 16 16" id="filter" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 6v9l-3.724-1.862A.5.5 0 0 1 6 12.691V6L1.854 1.854A.5.5 0 0 1 2.207 1h11.586a.5.5 0 0 1 .353.854L10 6z"/></symbol><symbol viewBox="0 0 16 16" id="folder" xmlns="http://www.w3.org/2000/svg"><path d="M7.228 5l-.475-1.335A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H7.228zM13 3a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="fork" xmlns="http://www.w3.org/2000/svg"><path d="M9 12.268a2 2 0 1 1-2 0V8.874A4.002 4.002 0 0 1 4 5V3.732a2 2 0 1 1 2 0V5a2 2 0 1 0 4 0V3.732a2 2 0 1 1 2 0V5a4.002 4.002 0 0 1-3 3.874v3.394zM11 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="geo-nodes" xmlns="http://www.w3.org/2000/svg"><path d="M9.7 13.1l-.2.2c-.7.8-2 .9-2.8.1-.1 0-.1-.1-.1-.1l-.2-.2c-2 .2-3.4.7-3.4 1.4 0 .8 2.2 1.5 5 1.5s5-.7 5-1.5c0-.7-1.4-1.2-3.3-1.4M7.3 12.7c.4.4 1 .3 1.4-.1C11.6 9.5 13 7 13 5.3 13 2.4 10.8 0 8 0S3 2.4 3 5.3C3 7 4.4 9.5 7.3 12.7M8 2c1.6 0 3 1.4 3 3.3 0 1-1 2.8-3 5.2-2-2.4-3-4.2-3-5.2C5 3.4 6.4 2 8 2"/><circle cx="8" cy="5" r="1"/></symbol><symbol viewBox="0 0 16 16" id="git-merge" xmlns="http://www.w3.org/2000/svg"><path d="M11 12.268V5a1 1 0 0 0-1-1v1a.5.5 0 0 1-.8.4l-2.667-2a.5.5 0 0 1 0-.8L9.2.6a.5.5 0 0 1 .8.4v1a3 3 0 0 1 3 3v7.268a2 2 0 1 1-2 0zm-6 0a2 2 0 1 1-2 0V4.732a2 2 0 1 1 2 0v7.536zM4 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="group" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.048 11.997C-.377 11.975.013 11.782.013 10.56.013 9.235.653 8 4 8c.444 0 .84.022 1.194.062.164.435.426.82.76 1.132-1.786.389-2.721 1.353-2.906 2.803zm2.94-7.222a2.993 2.993 0 0 0-.976 1.95 2 2 0 1 1 .975-1.95zm6.964 7.222c-.185-1.45-1.12-2.414-2.906-2.803.334-.311.596-.697.76-1.132C11.16 8.022 11.556 8 12 8c3.346 0 3.987 1.235 3.987 2.56 0 1.222.39 1.415-3.035 1.437zm-1.964-5.272a2.993 2.993 0 0 0-.976-1.95 2 2 0 1 1 .976 1.95zM8 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 5c-2.177 0-3.987-.115-3.987-1.44S4.653 10 8 10c3.346 0 3.987 1.235 3.987 2.56S10.177 14 8 14z"/></symbol><symbol viewBox="0 0 16 16" id="history" xmlns="http://www.w3.org/2000/svg"><path d="M2.868 3.24a7 7 0 1 1-.043 9.475 1 1 0 0 1 1.478-1.348 5 5 0 1 0 .124-6.865l.796.645a.5.5 0 0 1-.193.873l-3.232.814a.5.5 0 0 1-.622-.504L1.3 3a.5.5 0 0 1 .814-.37l.754.61zM9 8h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V6a1 1 0 1 1 2 0v2z"/></symbol><symbol viewBox="0 0 16 16" id="home" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177a.505.505 0 0 1-.038.044l.038-.044zm-.787 0l.038.043a.5.5 0 0 1-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="hook" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-block" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.803 8a5.97 5.97 0 0 0-.462 1H4.5a.5.5 0 0 1 0-1h1.303zM4.5 5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm7.5.083a6.04 6.04 0 0 0-2 0V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.083a5.96 5.96 0 0 0 .72 2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v2.083zm1.121 3.796zM11 16a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm-1.293-2.292a3 3 0 0 0 4.001-4.001l-4.001 4zm-1.415-1.415l4.001-4a3 3 0 0 0-4.001 4.001z"/></symbol><symbol viewBox="0 0 16 16" id="issue-child" xmlns="http://www.w3.org/2000/svg"><path d="M11 8H5v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V7a.997.997 0 0 1 1-1h3V4H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v2h3a.997.997 0 0 1 1 1v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm-9 3v2h3v-2H2zm9 0v2h3v-2h-3z"/></symbol><symbol viewBox="0 0 16 16" id="issue-close" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M10.874 2H12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-2c-.918 0-1.74-.413-2.29-1.063a3.987 3.987 0 0 0 1.988-.984A1 1 0 0 0 10 14h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-1V3c0-.345-.044-.68-.126-1zM4 0h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-new" xmlns="http://www.w3.org/2000/svg"><path d="M10 2V1a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V4H9a1 1 0 1 1 0-2h1zm0 6a1 1 0 0 1 2 0v5a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h1a1 1 0 1 1 0 2H5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open-m" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-parent" xmlns="http://www.w3.org/2000/svg"><path d="M11 11H5v1h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H3v-2a.997.997 0 0 1 1-1h3V7H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9v2h3a.997.997 0 0 1 1 1v2h2.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H11v-1zM6 3v2h4V3H6z"/></symbol><symbol viewBox="0 0 16 16" id="issues" xmlns="http://www.w3.org/2000/svg"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7.575 6.689a4.002 4.002 0 0 1 6.274-4.86 4 4 0 0 1-4.86 6.274l-2.21 2.21.706.708a1 1 0 1 1-1.414 1.414l-.707-.707-.707.707.707.707a1 1 0 1 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.414-1.414l5.746-5.746zm2.032-.618a2 2 0 1 0 2.828-2.828A2 2 0 0 0 9.607 6.07z"/></symbol><symbol viewBox="0 0 16 16" id="key-2" xmlns="http://www.w3.org/2000/svg"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></symbol><symbol viewBox="0 0 16 16" id="label" xmlns="http://www.w3.org/2000/svg"><path d="M11.782 14.718a3 3 0 0 1-4.242 0L1.652 8.829a2 2 0 0 1-.565-1.702l.54-3.703a2 2 0 0 1 1.69-1.69l3.703-.54a2 2 0 0 1 1.703.564l5.888 5.888a3 3 0 0 1 0 4.243l-2.829 2.829zm1.415-5.657L7.309 3.173l-3.703.54-.54 3.702 5.888 5.888a1 1 0 0 0 1.414 0l2.829-2.828a1 1 0 0 0 0-1.414zM5.732 5.525A1 1 0 1 1 7.146 6.94a1 1 0 0 1-1.414-1.414z"/></symbol><symbol viewBox="0 0 16 16" id="labels" xmlns="http://www.w3.org/2000/svg"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667zM.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="leave" xmlns="http://www.w3.org/2000/svg"><path d="M11 7V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43V9H7a1 1 0 1 1 0-2h4zm-2 6.256a1 1 0 0 1 2 0A2.744 2.744 0 0 1 8.256 16H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5.19A2.81 2.81 0 0 1 11 2.81a1 1 0 0 1-2 0A.81.81 0 0 0 8.19 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.256c.41 0 .744-.333.744-.744z"/></symbol><symbol viewBox="0 0 16 16" id="level-up" xmlns="http://www.w3.org/2000/svg"><path fill="#2E2E2E" fill-rule="evenodd" d="M7 6h3.489a.5.5 0 0 0 .373-.832L6.374.117a.5.5 0 0 0-.748 0l-4.488 5.05A.5.5 0 0 0 1.51 6H5v7a3 3 0 0 0 3 3h6a1 1 0 0 0 0-2H8a1 1 0 0 1-1-1V6z"/></symbol><symbol viewBox="0 0 16 16" id="license" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.56 8.9l2.66 4.606a.3.3 0 0 1-.243.45l-1.678.094a.1.1 0 0 0-.078.044l-.953 1.432a.3.3 0 0 1-.51-.016L9.097 10.9a5.994 5.994 0 0 0 3.464-2zm-5.23 2.063L4.707 15.51a.3.3 0 0 1-.51.016l-.953-1.432a.1.1 0 0 0-.078-.044l-1.678-.094a.3.3 0 0 1-.243-.45l2.48-4.297a5.983 5.983 0 0 0 3.607 1.754zM8 10A5 5 0 1 1 8 0a5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="link" xmlns="http://www.w3.org/2000/svg"><path d="M6.986 3.35l2.12-2.122a4 4 0 0 1 5.657 5.657l-2.828 2.829a4 4 0 0 1-5.657 0 1 1 0 0 1 1.414-1.415 2 2 0 0 0 2.829 0l2.828-2.828a2 2 0 1 0-2.828-2.828l-1.001 1a5.018 5.018 0 0 0-2.534-.294zm2.12 9.192l-2.12 2.121a4 4 0 1 1-5.658-5.656l2.829-2.829a4 4 0 0 1 5.657 0 1 1 0 1 1-1.415 1.414 2 2 0 0 0-2.828 0l-2.828 2.829a2 2 0 1 0 2.828 2.828l1.001-1.001a5.018 5.018 0 0 0 2.534.294z"/></symbol><symbol viewBox="0 0 16 16" id="list-bulleted" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-7h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 5h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm-4 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-2h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="list-numbered" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 0 1 0-2zM1.156 5v-.828h.816V2.204h-.72v-.636c.432-.084.708-.192.996-.372h.756v2.976h.684V5H1.156zm-.18 5v-.588c.9-.828 1.596-1.464 1.596-1.98 0-.342-.192-.504-.468-.504-.252 0-.444.18-.624.36l-.552-.552c.396-.42.756-.612 1.32-.612.768 0 1.308.492 1.308 1.248 0 .612-.576 1.284-1.092 1.812.192-.024.468-.048.636-.048h.636V10H.976zm1.26 5.072c-.618 0-1.068-.204-1.356-.54l.468-.648c.234.216.51.36.78.36.336 0 .552-.12.552-.36 0-.288-.15-.456-.948-.456v-.72c.636 0 .828-.168.828-.432 0-.228-.138-.348-.396-.348-.252 0-.432.108-.672.312l-.516-.624c.372-.312.768-.492 1.236-.492.84 0 1.38.384 1.38 1.074 0 .366-.204.642-.612.822v.024c.432.132.732.432.732.912 0 .72-.684 1.116-1.476 1.116z"/></symbol><symbol viewBox="0 0 16 16" id="location" xmlns="http://www.w3.org/2000/svg"><path d="M8.755 15.144a1 1 0 0 1-1.51 0C3.748 11.114 2 8.065 2 6a6 6 0 1 1 12 0c0 2.065-1.748 5.113-5.245 9.144zM12 6a4 4 0 1 0-8 0c0 1.314 1.312 3.71 4 6.944C10.688 9.71 12 7.314 12 6zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="location-dot" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.314 13.087C4.382 13.295 3 13.85 3 14.5c0 .828 2.239 1.5 5 1.5s5-.672 5-1.5c0-.65-1.382-1.205-3.314-1.413l-.202.225a2 2 0 0 1-2.968 0l-.202-.225zm2.428-.445a1 1 0 0 1-1.484 0C4.419 9.5 3 7.037 3 5.252 3 2.353 5.239 0 8 0s5 2.352 5 5.253c0 1.784-1.42 4.247-4.258 7.389zM11 5.252C11 3.436 9.634 2 8 2S5 3.435 5 5.253c0 1.027.974 2.824 3 5.203 2.026-2.38 3-4.176 3-5.203zM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M10 5V4h2v1a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3V4h2v1h4zM4 7a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H4zm0-3a4 4 0 1 1 8 0h-2a2 2 0 1 0-4 0H4z"/></symbol><symbol viewBox="0 0 16 16" id="lock-open" xmlns="http://www.w3.org/2000/svg"><path d="M4.044 4a4 4 0 0 1 6.99-2.658 1 1 0 1 1-1.495 1.33A2 2 0 0 0 6.044 4a.998.998 0 0 1-.07.367v.701H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-5a3 3 0 0 1 2.974-3V4h.07zM4 7.07a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="log" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="mail" xmlns="http://www.w3.org/2000/svg"><path d="M14 5.6L9.338 9.796a2 2 0 0 1-2.676 0L2 5.6V11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5.6zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm.212 2L8 8.31 12.788 4H3.212z"/></symbol><symbol viewBox="0 0 16 16" id="merge-request-close" xmlns="http://www.w3.org/2000/svg"><path d="M9.414 8l1.414 1.414a1 1 0 1 1-1.414 1.414L8 9.414l-1.414 1.414a1 1 0 1 1-1.414-1.414L6.586 8 5.172 6.586a1 1 0 1 1 1.414-1.414L8 6.586l1.414-1.414a1 1 0 1 1 1.414 1.414L9.414 8zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="merge-request-close-m" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.414 8l4.95-4.95a1 1 0 0 0-1.414-1.414L8 6.586l-4.95-4.95A1 1 0 0 0 1.636 3.05L6.586 8l-4.95 4.95a1 1 0 1 0 1.414 1.414L8 9.414l4.95 4.95a1 1 0 1 0 1.414-1.414L9.414 8z"/></symbol><symbol viewBox="0 0 16 16" id="messages" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="mobile-issue-close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.657 10.728L2.12 7.192A1 1 0 1 0 .707 8.607l4.243 4.242a.997.997 0 0 0 1.414 0l8.485-8.485a1 1 0 1 0-1.414-1.414l-7.778 7.778z"/></symbol><symbol viewBox="0 0 16 16" id="monitor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></symbol><symbol viewBox="0 0 16 16" id="more" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="notifications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></symbol><symbol viewBox="0 0 16 16" id="notifications-off" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.26 5.089c.243.757.382 1.478.5 2.017.187.86.74 2.188 1.66 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0H4.35l2-2h7.29c-.993-1.937-1.6-3.396-1.835-4.468-.07-.326-.129-.59-.178-.81l1.634-1.633zM10.943 1.75l-1.48 1.48C9.07 3.076 8.612 3 8.069 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.065.317-.17.673-.317 1.073L.45 12.242a1.99 1.99 0 0 1 .224-1.19c.962-1.787 1.521-3.064 1.68-3.831.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024 4.867 4.867 0 0 1 1.944.688zm2.932-.105a1 1 0 0 1 0 1.415L2.561 14.374a1 1 0 1 1-1.415-1.414L12.46 1.646a1 1 0 0 1 1.414 0z"/></symbol><symbol viewBox="0 0 16 16" id="overview" xmlns="http://www.w3.org/2000/svg"><path d="M2 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2h-3zM2 9h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3h-3z"/></symbol><symbol viewBox="0 0 16 16" id="pencil" xmlns="http://www.w3.org/2000/svg"><path d="M13.02 1.293l1.414 1.414a1 1 0 0 1 0 1.414L4.119 14.436a1 1 0 0 1-.704.293l-2.407.008L1 12.316a1 1 0 0 1 .293-.71L11.605 1.292a1 1 0 0 1 1.414 0zm-1.416 1.415l-.707.707L12.31 4.83l.707-.707-1.414-1.415zM3.411 13.73l1.123-1.122H3.12v-1.415L2 12.312l.005 1.422 1.406-.005z"/></symbol><symbol viewBox="0 0 16 16" id="pipeline" xmlns="http://www.w3.org/2000/svg"><path d="M8.969 7.25a2 2 0 1 1-1.938 0A1.002 1.002 0 0 1 7 7V5.083a.2.2 0 0 1 .06-.142l.877-.87a.1.1 0 0 1 .141 0l.864.87A.2.2 0 0 1 9 5.083V7c0 .086-.01.17-.031.25zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm4.5-4a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="play" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.765 15.835c-.545.321-1.258.159-1.593-.363A1.075 1.075 0 0 1 1 14.89V1.11C1 .496 1.518 0 2.158 0c.214 0 .424.057.607.165l11.684 6.89c.544.321.714 1.005.38 1.526a1.135 1.135 0 0 1-.38.364l-11.684 6.89z"/></symbol><symbol viewBox="0 0 16 16" id="plus" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V1a1 1 0 1 1 2 0v6h6a1 1 0 0 1 0 2H9v6a1 1 0 0 1-2 0V9H1a1 1 0 1 1 0-2h6z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 7V4a1 1 0 1 0-2 0v3H4a1 1 0 1 0 0 2h3v3a1 1 0 0 0 2 0V9h3a1 1 0 0 0 0-2H9zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square-o" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="preferences" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 12h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2zm11-5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zM6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2z"/></symbol><symbol viewBox="0 0 16 16" id="profile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="push-rules" xmlns="http://www.w3.org/2000/svg"><path d="M6.268 9a2 2 0 0 1 3.464 0H11a1 1 0 0 1 0 2H9.732a2 2 0 0 1-3.464 0H5a1 1 0 0 1 0-2h1.268zM7 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-1v3.515a.3.3 0 0 1-.434.268l-1.432-.716a.3.3 0 0 0-.268 0l-1.432.716A.3.3 0 0 1 7 5.515V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm4 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="question" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm-1.46-5.602h2.233a3.97 3.97 0 0 1 .051-.558c.029-.17.073-.326.133-.469.06-.143.14-.28.242-.41.102-.13.228-.263.38-.399.26-.24.504-.467.733-.683a5.03 5.03 0 0 0 .598-.668c.17-.23.302-.477.399-.742a2.66 2.66 0 0 0 .144-.907c0-.505-.083-.95-.25-1.335a2.55 2.55 0 0 0-.723-.97 3.2 3.2 0 0 0-1.152-.589 5.441 5.441 0 0 0-1.531-.2c-.516 0-.998.063-1.445.188a3.19 3.19 0 0 0-1.168.59c-.331.268-.594.61-.79 1.027-.195.417-.295.917-.3 1.5h2.64c.006-.224.04-.416.102-.578.062-.161.142-.293.238-.394a.921.921 0 0 1 .332-.227 1.04 1.04 0 0 1 .39-.074c.34 0 .593.095.763.285.169.19.254.488.254.895 0 .328-.106.63-.317.906-.21.276-.499.565-.863.867-.214.182-.39.374-.531.574-.141.2-.253.42-.336.657a3.656 3.656 0 0 0-.176.777 7.89 7.89 0 0 0-.05.937zm-.321 2.375c0 .188.035.362.105.524.07.161.17.3.301.418.13.117.284.21.46.277.178.068.376.102.595.102.218 0 .416-.034.593-.102.178-.068.331-.16.461-.277a1.2 1.2 0 0 0 .301-.418c.07-.162.106-.336.106-.524a1.3 1.3 0 0 0-.106-.523 1.2 1.2 0 0 0-.3-.418 1.461 1.461 0 0 0-.462-.277 1.651 1.651 0 0 0-.593-.102c-.22 0-.417.034-.594.102a1.46 1.46 0 0 0-.461.277 1.2 1.2 0 0 0-.3.418 1.284 1.284 0 0 0-.106.523z"/></symbol><symbol viewBox="0 0 16 16" id="question-o" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-.778-4.151c0-.301.014-.575.044-.82a3.2 3.2 0 0 1 .154-.68c.073-.208.17-.4.294-.575.123-.176.278-.343.465-.503a4.81 4.81 0 0 0 .755-.758c.185-.242.277-.506.277-.793 0-.356-.074-.617-.222-.783-.148-.166-.37-.25-.667-.25a.92.92 0 0 0-.342.065.806.806 0 0 0-.29.199 1.04 1.04 0 0 0-.209.345 1.5 1.5 0 0 0-.088.506H5.082c.005-.51.092-.948.263-1.313.171-.364.401-.664.69-.899.29-.234.63-.406 1.023-.516a4.66 4.66 0 0 1 1.264-.164c.497 0 .944.058 1.34.174.397.117.733.289 1.008.517.276.227.487.51.633.847.146.337.218.727.218 1.17 0 .295-.042.56-.126.792a2.52 2.52 0 0 1-.349.65 4.4 4.4 0 0 1-.523.584c-.2.19-.414.389-.642.598a2.73 2.73 0 0 0-.332.349c-.089.114-.16.233-.212.359a1.868 1.868 0 0 0-.116.41 3.39 3.39 0 0 0-.044.489H7.222zm-.28 2.078c0-.164.03-.317.092-.458a1.05 1.05 0 0 1 .263-.366c.114-.103.248-.183.403-.243a1.45 1.45 0 0 1 .52-.089c.191 0 .364.03.52.09.154.059.289.14.403.242.114.103.201.224.263.366.061.141.092.294.092.458 0 .164-.03.316-.092.458a1.05 1.05 0 0 1-.263.365 1.278 1.278 0 0 1-.404.243 1.43 1.43 0 0 1-.52.089c-.19 0-.364-.03-.519-.089-.155-.06-.29-.14-.403-.243a1.05 1.05 0 0 1-.263-.365 1.135 1.135 0 0 1-.093-.458z"/></symbol><symbol viewBox="0 0 16 16" id="quote" xmlns="http://www.w3.org/2000/svg"><path d="M15 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1zM7 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="redo" xmlns="http://www.w3.org/2000/svg"><path d="M4.666 4.423a5 5 0 1 1-.203 6.944 1 1 0 1 0-1.478 1.347 7 7 0 1 0 .12-9.556L1.842 2.137a.5.5 0 0 0-.815.385L1 7.26a.5.5 0 0 0 .607.492l4.629-1.013a.5.5 0 0 0 .207-.877L4.666 4.423z"/></symbol><symbol viewBox="0 0 16 16" id="remove" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2v10a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V3zm3-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5zM4 3v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3H4zm2.5 2a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="repeat" xmlns="http://www.w3.org/2000/svg"><path d="M11.494 4.423a5 5 0 1 0 .203 6.944 1 1 0 1 1 1.478 1.347 7 7 0 1 1-.12-9.556l1.262-1.021a.5.5 0 0 1 .815.385l.028 4.738a.5.5 0 0 1-.607.492L9.924 6.739a.5.5 0 0 1-.207-.877l1.777-1.439z"/></symbol><symbol viewBox="0 0 16 16" id="retry" xmlns="http://www.w3.org/2000/svg"><path d="M4.009 6.958a4 4 0 0 0 5.283 4.775 1 1 0 0 1 .712 1.87A6 6 0 0 1 2.077 6.44l-.741-.2a.5.5 0 0 1-.12-.915L3.41 4.058a.5.5 0 0 1 .683.183l1.268 2.196a.5.5 0 0 1-.563.733l-.79-.212zm7.777 2.084a4 4 0 0 0-5.284-4.775 1 1 0 0 1-.711-1.87 6 6 0 0 1 7.927 7.162l.74.2a.5.5 0 0 1 .121.915l-2.196 1.268a.5.5 0 0 1-.683-.183l-1.267-2.196a.5.5 0 0 1 .562-.733l.79.212z"/></symbol><symbol viewBox="0 0 16 16" id="scale" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.99 9a.792.792 0 0 0-.078-.231L13 7l-.912 1.769a.791.791 0 0 0-.077.231h1.978zm-10 0a.792.792 0 0 0-.078-.231L3 7l-.912 1.769A.791.791 0 0 0 2.011 9h1.978zM2 0h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm3 14h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zM8 4a1 1 0 0 1 1 1v9H7V5a1 1 0 0 1 1-1zm-4.53-.714l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 3 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158L2.53 3.286a.53.53 0 0 1 .94 0zm10 0l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 13 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158l2.266-4.735a.53.53 0 0 1 .94 0z"/></symbol><symbol viewBox="0 0 16 16" id="screen-full" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14 14v-2a1 1 0 0 1 2 0v3a.997.997 0 0 1-1 1h-3a1 1 0 0 1 0-2h2zM2 14v-2a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1h3a1 1 0 0 0 0-2H2zM15.707.293A.997.997 0 0 1 16 1v3a1 1 0 0 1-2 0V2h-2a1 1 0 0 1 0-2h3c.276 0 .526.112.707.293zM2 2v2a1 1 0 1 1-2 0V1a.997.997 0 0 1 1-1h3a1 1 0 1 1 0 2H2zm4 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="screen-normal" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3V1a1 1 0 1 1 2 0v3a.997.997 0 0 1-1 1H1a1 1 0 1 1 0-2h2zm10 0h2a1 1 0 0 1 0 2h-3a.997.997 0 0 1-1-1V1a1 1 0 0 1 2 0v2zM3 13H1a1 1 0 0 1 0-2h3a.997.997 0 0 1 1 1v3a1 1 0 0 1-2 0v-2zm10 0v2a1 1 0 0 1-2 0v-3a.997.997 0 0 1 1-1h3a1 1 0 0 1 0 2h-2zM6.5 7h3a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="search" xmlns="http://www.w3.org/2000/svg"><path d="M8.853 8.854a3.5 3.5 0 1 0-4.95-4.95 3.5 3.5 0 0 0 4.95 4.95zm.207 2.328a5.5 5.5 0 1 1 2.121-2.121l3.329 3.328a1.5 1.5 0 0 1-2.121 2.121L9.06 11.182z"/></symbol><symbol viewBox="0 0 16 16" id="settings" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918a5.97 5.97 0 0 1 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="shield" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v7.186a3 3 0 0 1-1.426 2.554l-4 2.465a3 3 0 0 1-3.148 0l-4-2.465A3 3 0 0 1 1 10.186V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v7.186a1 1 0 0 0 .475.852l4 2.464a1 1 0 0 0 1.05 0l4-2.464a1 1 0 0 0 .475-.852V3a1 1 0 0 0-1-1H4zm0 1.5a.5.5 0 0 1 .5-.5h4v8.837a.5.5 0 0 1-.753.431l-3.5-2.052A.5.5 0 0 1 4 9.785V3.5z"/></symbol><symbol viewBox="0 0 16 16" id="slight-frown" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="slight-smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-5.163 2.254a.5.5 0 1 1 .865-.502 1.499 1.499 0 0 0 2.607-.018.5.5 0 1 1 .871.49 2.499 2.499 0 0 1-4.343.03z"/></symbol><symbol viewBox="0 0 16 16" id="smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6.18 6.27a.5.5 0 0 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zm6 0a.5.5 0 1 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zM5 9a3 3 0 0 0 6 0H5z"/></symbol><symbol viewBox="0 0 16 16" id="smiley" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5 9h6a3 3 0 0 1-6 0z"/></symbol><symbol viewBox="0 0 16 16" id="snippet" xmlns="http://www.w3.org/2000/svg"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 0 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 0 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></symbol><symbol viewBox="0 0 16 16" id="spam" xmlns="http://www.w3.org/2000/svg"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="star" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.609 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="star-o" xmlns="http://www.w3.org/2000/svg"><path d="M10.975 10.99a3 3 0 0 1 .655-2.083l1.54-1.916-2.219-.576a3 3 0 0 1-1.825-1.37L8 3.15 6.874 5.044a3 3 0 0 1-1.825 1.371l-2.218.576 1.54 1.916a3 3 0 0 1 .654 2.083l-.165 2.4 1.965-.836a3 3 0 0 1 2.348 0l1.965.836-.164-2.399zM7.61 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="stop" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="talic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 0h7a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm2 2h3L8 14H5L8 2zM3 14h7a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="task-done" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="template" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="thump-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 11h5.282a2 2 0 0 0 1.963-2.38l-.563-2.905a3 3 0 0 0-.243-.732l-1.103-2.286A3 3 0 0 0 10.964 1H7a3 3 0 0 0-3 3v6.3a2 2 0 0 0 .436 1.247l3.11 3.9a.632.632 0 0 0 .941.053l.137-.137a1 1 0 0 0 .28-.87L8.329 11zM1 10h2V3H1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="thump-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.103 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.487.5l.137.137a1 1 0 0 1 .28.87L8.329 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="timer" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 3.27l.77-.77a1 1 0 0 1 1.415 1.414l-.728.729a7 7 0 1 1-1.456-1.372zM8 14A5 5 0 1 0 8 4a5 5 0 0 0 0 10zm0-9a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zM6 0h4a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-add" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 4V2a1 1 0 0 1 2 0v2h2a1 1 0 0 1 0 2h-2v2a1 1 0 0 1-2 0V6H8a1 1 0 1 1 0-2h2zm2 7a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-done" xmlns="http://www.w3.org/2000/svg"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0L4.707 6.778a1 1 0 0 1 1.414-1.414l2.122 2.121zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="token" xmlns="http://www.w3.org/2000/svg"><path d="M3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm1 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="unapproval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.95 8.536l1.06-1.061a1 1 0 0 1 1.415 1.414l-1.061 1.06 1.06 1.061a1 1 0 0 1-1.414 1.415l-1.06-1.061-1.06 1.06a1 1 0 1 1-1.415-1.414l1.06-1.06-1.06-1.06a1 1 0 0 1 1.414-1.415l1.06 1.06zm-3.768-.33c.006.503.201 1.006.586 1.39l.353.354-.353.353a2 2 0 1 0 2.828 2.829l.354-.354.047.048C11.964 14.363 11.527 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.834 0 1.557.074 2.182.205zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="unassignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 5h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="unlink" xmlns="http://www.w3.org/2000/svg"><path d="M11.295 8.845l-.659-1.664a1.78 1.78 0 0 0 .04-.04l1.415-1.414c.586-.586.654-1.468.152-1.97s-1.384-.434-1.97.152L8.859 5.323a1.781 1.781 0 0 0-.04.04l-1.664-.658c.141-.208.305-.408.491-.594l1.415-1.414c1.366-1.367 3.424-1.525 4.596-.354 1.171 1.172 1.013 3.23-.354 4.596L11.89 8.354c-.186.186-.386.35-.594.491zm-2.45 2.45a4.075 4.075 0 0 1-.491.594l-1.415 1.414c-1.366 1.367-3.424 1.525-4.596.354-1.171-1.172-1.013-3.23.354-4.596L4.11 7.646c.186-.186.386-.35.594-.491l.659 1.664a1.781 1.781 0 0 0-.04.04l-1.415 1.414c-.586.586-.654 1.468-.152 1.97s1.384.434 1.97-.152l1.414-1.414a1.78 1.78 0 0 0 .04-.04l1.664.658zm3.812-2.088h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-.05a.5.5 0 0 1 .5-.5zm-.384 2.116l1.415 1.414a.5.5 0 0 1 0 .708l-.037.036a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 0-.707l.036-.037a.5.5 0 0 1 .707 0zm-2.823 1.09a.5.5 0 0 1 .5-.5h.052a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9.95a.5.5 0 0 1-.5-.5v-2zm-2.748-9.16a.5.5 0 0 1-.5.5h-.05a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h.05a.5.5 0 0 1 .5.5v2zm-2.116.383a.5.5 0 0 1 0 .707l-.036.036a.5.5 0 0 1-.707 0L2.428 2.965a.5.5 0 0 1 0-.707l.037-.036a.5.5 0 0 1 .707 0l1.414 1.414zm-1.09 2.823h-2a.5.5 0 0 1-.5-.5v-.051a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5z"/></symbol><symbol viewBox="0 0 16 16" id="user" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 8c-6.888 0-6.976-.78-6.976-2.52S2.144 8 8 8s6.976 2.692 6.976 4.48c0 1.788-.088 2.52-6.976 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="users" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="volume-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 5h1v6H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm2 0l4.445-2.964A1 1 0 0 1 9 2.87v10.26a1 1 0 0 1-1.555.833L3 11V5zm10.283 7.89a.5.5 0 0 1-.66-.752A5.485 5.485 0 0 0 14.5 8c0-1.601-.687-3.09-1.865-4.128a.5.5 0 0 1 .661-.75A6.484 6.484 0 0 1 15.5 8a6.485 6.485 0 0 1-2.217 4.89zm-2.002-2.236a.5.5 0 1 1-.652-.758c.55-.472.871-1.157.871-1.896 0-.732-.315-1.411-.856-1.883a.5.5 0 0 1 .658-.753A3.492 3.492 0 0 1 12.5 8c0 1.033-.45 1.994-1.219 2.654z"/></symbol><symbol viewBox="0 0 16 16" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M15.34 10.479A3 3 0 0 1 12.756 15h-9.51A3 3 0 0 1 .66 10.479l4.755-8.083a3 3 0 0 1 5.172 0l4.755 8.083zm-6.478-7.07a1 1 0 0 0-1.724 0l-4.755 8.084A1 1 0 0 0 3.245 13h9.51a1 1 0 0 0 .862-1.507L8.862 3.41zM8 5a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="work" xmlns="http://www.w3.org/2000/svg"><path d="M12 3h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h1V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zM6 2v1h4V2H6zM3 5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H3zm1.5 1a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm7 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol></svg> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 16 16" id="abuse" xmlns="http://www.w3.org/2000/svg"><path d="M11.408.328l4.029 3.222A1.5 1.5 0 0 1 16 4.72v6.555a1.5 1.5 0 0 1-.563 1.171l-4.026 3.224a1.5 1.5 0 0 1-.937.329H5.529a1.5 1.5 0 0 1-.937-.328L.563 12.45A1.5 1.5 0 0 1 0 11.28V4.724a1.5 1.5 0 0 1 .563-1.171L4.589.329A1.5 1.5 0 0 1 5.526 0h4.945c.34 0 .67.116.937.328zM10.296 2H5.702L2 4.964v6.074L5.704 14h4.594L14 11.036V4.962L10.296 2zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="account" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.195 9.965l-.568-.875a.25.25 0 0 1 .015-.294l.405-.5a.25.25 0 0 1 .283-.075l.938.36c.257-.183.543-.325.851-.42l.322-.988A.25.25 0 0 1 11.679 7h.642a.25.25 0 0 1 .238.173l.322.988c.308.095.594.237.851.42l.938-.36a.25.25 0 0 1 .283.076l.405.5a.25.25 0 0 1 .015.293l-.568.875c.113.297.18.616.193.95l.898.54a.25.25 0 0 1 .115.27l-.144.626a.25.25 0 0 1-.222.193l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.281a.25.25 0 0 1-.29-.05l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.904a.25.25 0 0 1-.289.051l-.577-.281a.25.25 0 0 1-.138-.26l.165-1.18a3.015 3.015 0 0 1-.512-.607l-1.115-.098a.25.25 0 0 1-.222-.193l-.144-.626a.25.25 0 0 1 .115-.27l.898-.54c.013-.334.08-.653.193-.95zM6.789 8.023A12.845 12.845 0 0 0 6 8c-5.036 0-6 2.74-6 4.48C0 14.22.076 15 6 15c.553 0 1.055-.006 1.51-.02A5.977 5.977 0 0 1 6 11c0-1.083.287-2.1.79-2.977zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM12 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="admin" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.162 2.5a3.5 3.5 0 0 1-3.163 5.479L6.08 14.766a1.5 1.5 0 0 1-2.598-1.5L7.4 6.479A3.5 3.5 0 0 1 10.564 1L8.9 3.88l2.599 1.5 1.663-2.88zm-8.63 11.949a.5.5 0 1 0 .5-.866.5.5 0 0 0-.5.866z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.414 7.95l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 1 0 1.414-1.415L10.414 7.95zm-7 0l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 0 0 1.414-1.415L3.414 7.95z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.535 7.95L1.292 3.707a1 1 0 0 1-.281-.695 1 1 0 1 1 1.695-.719l4.95 4.95a.998.998 0 0 1 0 1.414l-4.95 4.95a1.002 1.002 0 0 1-.707.293c-.549 0-1-.452-1-1 0-.266.106-.52.293-.708L5.535 7.95zm7 0L8.292 3.707a1 1 0 0 1-.281-.695 1 1 0 1 1 1.695-.719l4.95 4.95a.998.998 0 0 1 0 1.414l-4.95 4.95a1.002 1.002 0 0 1-.707.293c-.549 0-1-.452-1-1.001 0-.265.106-.519.293-.707l4.243-4.242z"/></symbol><symbol viewBox="0 0 16 16" id="angle-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></symbol><symbol viewBox="0 0 16 16" id="angle-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.757 8l4.95-4.95a1 1 0 1 0-1.414-1.414L3.636 7.293a.997.997 0 0 0 0 1.414l5.657 5.657a1 1 0 0 0 1.414-1.414L5.757 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.243 8l-4.95-4.95a1 1 0 0 1 1.414-1.414l5.657 5.657a.997.997 0 0 1 0 1.414l-5.657 5.657a1 1 0 0 1-1.414-1.414L10.243 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 6.757l-4.95 4.95a1 1 0 1 1-1.414-1.414l5.657-5.657a.997.997 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L8 6.757z"/></symbol><symbol viewBox="0 0 16 16" id="appearance" xmlns="http://www.w3.org/2000/svg"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858a23.85 23.85 0 0 1-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023a9.7 9.7 0 0 0 .478-.035c-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="applications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="approval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.536 10.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 1 1 9.12 9.243l1.415 1.414zM7.632 8.109A2 2 0 0 0 7 11.364l2.121 2.121a1.996 1.996 0 0 0 2.807.021C11.686 14.554 10.627 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.6 0 1.142.038 1.632.109zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></symbol><symbol viewBox="0 0 16 16" id="assignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 5V4a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V7h-1a1 1 0 0 1 0-2h1zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="bold" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 15V1a1 1 0 0 1 1-1h4.604c.93 0 1.762.088 2.495.264.733.176 1.353.445 1.863.807.509.363.897.82 1.164 1.369.268.549.401 1.197.401 1.945 0 .366-.045.718-.137 1.055-.091.337-.23.652-.417.945a3.453 3.453 0 0 1-.71.796 3.645 3.645 0 0 1-1.021.588c.469.117.87.295 1.203.533.333.238.608.515.824.83.216.315.374.657.473 1.027.099.37.148.75.148 1.138 0 1.553-.5 2.725-1.5 3.516-1 .791-2.423 1.187-4.27 1.187H3a1 1 0 0 1-1-1zm3.297-5.967v4.319H8.12c.425 0 .791-.053 1.099-.16.307-.106.564-.252.769-.44.205-.186.357-.406.456-.659.099-.252.148-.529.148-.83a3.04 3.04 0 0 0-.131-.928 1.78 1.78 0 0 0-.413-.703 1.8 1.8 0 0 0-.73-.445c-.3-.103-.66-.154-1.077-.154H5.297zm0-2.33h2.44c.842-.014 1.468-.192 1.878-.533.41-.34.616-.826.616-1.456 0-.725-.21-1.247-.632-1.566-.421-.318-1.086-.478-1.995-.478H5.297v4.033z"/></symbol><symbol viewBox="0 0 16 16" id="book" xmlns="http://www.w3.org/2000/svg"><path d="M7 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 7 6.191V2zM5 0h6a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="branch" xmlns="http://www.w3.org/2000/svg"><path d="M6 11.978v.29a2 2 0 1 1-2 0V3.732a2 2 0 1 1 2 0v3.849c.592-.491 1.31-.854 2.15-1.081 1.308-.353 1.875-.882 1.893-1.743a2 2 0 1 1 2.002-.051C12.053 6.54 10.857 7.84 8.67 8.43 7.056 8.867 6.195 9.98 6 11.978zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm6 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 15a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="calendar" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 2h2a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2h2V1a1 1 0 1 1 2 0v1h4V1a1 1 0 1 1 2 0v1zM0 4h16v9a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4zm2 2.5V13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5zM5 8h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="cancel" xmlns="http://www.w3.org/2000/svg"><path d="M3.11 4.523a6 6 0 0 0 8.367 8.367L3.109 4.524zM4.522 3.11l8.368 8.368A6 6 0 0 0 4.524 3.11zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.078 8.2l3.535-3.536a2 2 0 0 1 2.828 2.828l-4.949 4.95c-.39.39-.902.586-1.414.586a1.994 1.994 0 0 1-1.414-.586l-4.95-4.95a2 2 0 1 1 2.828-2.828l3.536 3.535z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.977 7.998l3.535-3.535a2 2 0 1 0-2.828-2.828l-4.95 4.949c-.39.39-.586.902-.586 1.414 0 .512.196 1.024.586 1.414l4.95 4.95a2 2 0 1 0 2.828-2.828L7.977 7.998z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.22 7.998L4.683 4.463a2 2 0 0 1 2.828-2.828l4.95 4.949c.39.39.586.902.586 1.414a1.99 1.99 0 0 1-.586 1.414l-4.95 4.95a2 2 0 0 1-2.828-2.828l3.535-3.536z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.778 8.957l3.535 3.535a2 2 0 1 0 2.828-2.828l-4.949-4.95a1.994 1.994 0 0 0-1.414-.586c-.512 0-1.024.196-1.414.586l-4.95 4.95a2 2 0 1 0 2.828 2.828l3.536-3.535z"/></symbol><symbol viewBox="0 0 16 16" id="clock" xmlns="http://www.w3.org/2000/svg"><path d="M9 7h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V5a1 1 0 1 1 2 0v2zm-1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.414 8l4.95-4.95a1 1 0 0 0-1.414-1.414L8 6.586l-4.95-4.95A1 1 0 0 0 1.636 3.05L6.586 8l-4.95 4.95a1 1 0 1 0 1.414 1.414L8 9.414l4.95 4.95a1 1 0 1 0 1.414-1.414L9.414 8z"/></symbol><symbol viewBox="0 0 16 16" id="code" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.871 8.243a.997.997 0 0 0-.293-.707L12.75 4.707a1 1 0 0 0-1.414 1.414l2.12 2.122-2.12 2.121a1 1 0 0 0 1.414 1.414l2.828-2.828a.997.997 0 0 0 .293-.707zm-13.243 0L4.75 6.12a1 1 0 1 0-1.414-1.414L.507 7.536a.997.997 0 0 0 0 1.414l2.829 2.828a1 1 0 1 0 1.414-1.414L2.628 8.243zm6.407-4.107a1 1 0 0 1 .707 1.225L8.19 11.157a1 1 0 1 1-1.931-.518L7.81 4.843a1 1 0 0 1 1.224-.707z"/></symbol><symbol viewBox="0 0 16 16" id="comment" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comment-dots" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586zM5 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="comment-next" xmlns="http://www.w3.org/2000/svg"><path d="M8 5V4a.5.5 0 0 1 .8-.4l2.667 2a.5.5 0 0 1 0 .8L8.8 8.4A.5.5 0 0 1 8 8V7H6a1 1 0 1 1 0-2h2zM1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comments" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.75 10L0 13V3a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3.75zM13 5h1a2 2 0 0 1 2 2v8l-2.667-2H8a2 2 0 0 1-2-2h4a3 3 0 0 0 3-3V5z"/></symbol><symbol viewBox="0 0 16 16" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.876-1.008a4.002 4.002 0 0 1-7.752 0A1.01 1.01 0 0 1 4 9H1a1 1 0 1 1 0-2h3c.042 0 .083.003.124.008a4.002 4.002 0 0 1 7.752 0A1.01 1.01 0 0 1 12 7h3a1 1 0 0 1 0 2h-3a1.01 1.01 0 0 1-.124-.008z"/></symbol><symbol viewBox="0 0 16 16" id="credit-card" xmlns="http://www.w3.org/2000/svg"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="disk" xmlns="http://www.w3.org/2000/svg"><path d="M16 11.764V3a3 3 0 0 0-3-3H3a3 3 0 0 0-3 3v8.764A2.989 2.989 0 0 1 2 11V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v8c.768 0 1.47.289 2 .764zM2 12h12a2 2 0 1 1 0 4H2a2 2 0 1 1 0-4zm10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="doc_code" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zm1.036 7.607a.498.498 0 0 1-.147.354l-1.414 1.414a.5.5 0 0 1-.707-.707l1.06-1.06-1.06-1.061a.5.5 0 0 1 .707-.707l1.414 1.414a.498.498 0 0 1 .147.353zm-4.822 0l1.06 1.061a.5.5 0 0 1-.706.707l-1.414-1.414a.498.498 0 0 1 0-.707l1.414-1.414a.5.5 0 1 1 .707.707l-1.06 1.06zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="doc_image" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM7.333 9.667l1.313-1.313a.5.5 0 0 1 .708 0L12 11H4l2.188-1.75a.5.5 0 0 1 .624 0l.521.417zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 8a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4 11h8v.7a.3.3 0 0 1-.3.3H4.3a.3.3 0 0 1-.3-.3V11z"/></symbol><symbol viewBox="0 0 16 16" id="doc_text" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="download" xmlns="http://www.w3.org/2000/svg"><path d="M9 12h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0l-2-2.667A.5.5 0 0 1 6 12h1V8a1 1 0 1 1 2 0v4zM4 9a1 1 0 1 1 0 2 4 4 0 0 1-1.971-7.481 4 4 0 0 1 6.633-2.505 3.999 3.999 0 0 1 3.82 2.014A4 4 0 0 1 12 11a1 1 0 0 1 0-2 2 2 0 1 0 0-4h-1a2 2 0 0 0-3.112-1.662A2 2 0 1 0 4.268 5H4a2 2 0 1 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-3a1 1 0 0 1-1-1V6H8.527A.527.527 0 0 0 8 6.527V13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3zm-4-7H8.527c-.18 0-.355.013-.527.04V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2v2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3zM8.527 4h2.323a.5.5 0 0 1 .35.143l4.65 4.551a.5.5 0 0 1 .15.357V13a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6.527A2.527 2.527 0 0 1 8.527 4z"/></symbol><symbol viewBox="0 0 16 16" id="earth" xmlns="http://www.w3.org/2000/svg"><path d="M8.7 2.04l-.082.177c.283.223.422.413.417.571-.008.237-.311.057-.444.274-.133.218.038.542-.112.637-.15.096-.398-.386-.479-.46-.054-.049-.166-.257-.336-.625l-.216-.225a.844.844 0 0 0-.418-.035c-.177.038-.075.1-.035.132.04.032.32.037.452.2.132.164.03.224-.05.298-.054.05-.157.062-.31.035H5.952l-.402.398.03.325.229.455.324-.463c.008-.206.058-.342.15-.41.14-.1.342-.15.534-.085.191.066-.057.218.011.271.068.053.204-.098.313-.02.11.08.07.155.104.322.036.167.254.114.398.328.144.215.19.29.147.483-.043.195-.168.26-.305.232-.138-.028-.107-.246-.275-.348-.168-.102-.266-.114-.386-.054-.12.06-.016.129.023.235.04.106.274.321.224.43-.05.107-.108.116-.42 0-.21-.077-.414-.007-.615.212l-.76.722c-.153.715-.3 1.13-.44 1.243-.211.17-.177-.483-.483-.656-.306-.174-.494-.047-.8-.07-.307-.023-.42.65-.38.873a.434.434 0 0 0 .221.321c.236-.141.39-.184.465-.128.11.084-.144.267-.074.425.07.158.314.069.386.283.073.213.084.48-.05.706-.135.227-.275.178-.4.053-.127-.126-.033-.375-.255-.704-.223-.329-.381-.337-.63-.787-.158-.287-.35-.743-.575-1.366a6 6 0 0 0 3.21 7.198l.001-.075c0-.577-.004-.944-.012-1.102-.011-.236-.95-.945-1.104-1.2-.154-.256-.34-.595-.355-.746-.016-.151.185-.232.344-.325.16-.093-.11-.367.028-.626.137-.258.395-.438.496-.356.101.081.058.228.267.333.209.104.077-.213.456-.178.38.035.143.201.252.216.11.016.113-.127.299-.143.186-.015.282.445.471.622.19.178.452.008.611.043.159.034.267.09.402.255.136.166-.03.352.073.557.103.205 1.07.22 1.433.255.364.034.371.011.371.324s-.166.314-.453.507c-.286.193-.166.462-.38.762-.212.3-.316.062-.622.14-.306.077-.413.382-.452.568-.039.186-.386.094-.877.232-.29.082-.429.144-.569.204a6.002 6.002 0 0 0 7.682-4.3c-.094-.384-.18-.63-.258-.74-.213-.297-.36.21-.924.49-.564.278-.57-.288-.81-.49-.16-.133-.212-.44-.158-.92-.005-.478.02-.828.077-1.049.057-.221.126-.543.207-.965.351-.373.606-.572.764-.595.237-.034.336.374.658.3a.315.315 0 0 0 .035-.01 5.993 5.993 0 0 0-.475-.824l-.309-.043a.646.646 0 0 0-.332-.117c-.205-.02-.025.128-.089.24-.064.112-.235.724-.437.685-.201-.039-.204-.374-.17-.668.036-.294-.077-.35-.2-.412-.124-.062-.325-.213-.556-.295-.232-.082-.123-.175-.093-.274.03-.1.208-.015.193-.058-.014-.044-.313-.135-.266-.167.03-.02.2-.02.506.003l.216-.012.293-.163a.58.58 0 0 0-.376-.22c-.233-.036-.513-.034-.73-.142-.205-.103-.458-.36-.643-.638A5.965 5.965 0 0 0 8.7 2.04zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="eye" xmlns="http://www.w3.org/2000/svg"><path d="M8 14C4.816 14 2.253 12.284.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2s5.747 1.716 7.607 5.019a2 2 0 0 1 0 1.962C13.747 12.284 11.184 14 8 14zm0-2c2.41 0 4.338-1.29 5.864-4C12.338 5.29 10.411 4 8 4 5.59 4 3.662 5.29 2.136 8 3.662 10.71 5.589 12 8 12zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm1-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M13.618 2.62L1.62 14.619a1 1 0 0 1-.985-1.668l1.525-1.526C1.516 10.742.926 9.927.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2c1.074 0 2.076.195 3.006.58l.944-.944a1 1 0 0 1 1.668.985zM8.068 11a3 3 0 0 0 2.931-2.932l-2.931 2.931zm-3.02-2.462a3 3 0 0 1 3.49-3.49l.884-.884A6.044 6.044 0 0 0 8 4C5.59 4 3.662 5.29 2.136 8c.445.79.924 1.46 1.439 2.011l1.473-1.473zm.421 5.06l1.658-1.658c.283.04.575.06.873.06 2.41 0 4.338-1.29 5.864-4a11.023 11.023 0 0 0-1.133-1.664l1.418-1.418a12.799 12.799 0 0 1 1.458 2.1 2 2 0 0 1 0 1.963C13.747 12.284 11.184 14 8 14a7.883 7.883 0 0 1-2.53-.402z"/></symbol><symbol viewBox="0 0 16 16" id="file-additions" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3z"/></symbol><symbol viewBox="0 0 16 16" id="file-deletion" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm2 6h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="file-modified" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm5 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></symbol><symbol viewBox="0 0 16 16" id="filter" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 6v9l-3.724-1.862A.5.5 0 0 1 6 12.691V6L1.854 1.854A.5.5 0 0 1 2.207 1h11.586a.5.5 0 0 1 .353.854L10 6z"/></symbol><symbol viewBox="0 0 16 16" id="folder" xmlns="http://www.w3.org/2000/svg"><path d="M7.228 5l-.475-1.335A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H7.228zM13 3a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="fork" xmlns="http://www.w3.org/2000/svg"><path d="M9 12.268a2 2 0 1 1-2 0V8.874A4.002 4.002 0 0 1 4 5V3.732a2 2 0 1 1 2 0V5a2 2 0 1 0 4 0V3.732a2 2 0 1 1 2 0V5a4.002 4.002 0 0 1-3 3.874v3.394zM11 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="geo-nodes" xmlns="http://www.w3.org/2000/svg"><path d="M9.7 13.1l-.2.2c-.7.8-2 .9-2.8.1-.1 0-.1-.1-.1-.1l-.2-.2c-2 .2-3.4.7-3.4 1.4 0 .8 2.2 1.5 5 1.5s5-.7 5-1.5c0-.7-1.4-1.2-3.3-1.4M7.3 12.7c.4.4 1 .3 1.4-.1C11.6 9.5 13 7 13 5.3 13 2.4 10.8 0 8 0S3 2.4 3 5.3C3 7 4.4 9.5 7.3 12.7M8 2c1.6 0 3 1.4 3 3.3 0 1-1 2.8-3 5.2-2-2.4-3-4.2-3-5.2C5 3.4 6.4 2 8 2"/><circle cx="8" cy="5" r="1"/></symbol><symbol viewBox="0 0 16 16" id="git-merge" xmlns="http://www.w3.org/2000/svg"><path d="M11 12.268V5a1 1 0 0 0-1-1v1a.5.5 0 0 1-.8.4l-2.667-2a.5.5 0 0 1 0-.8L9.2.6a.5.5 0 0 1 .8.4v1a3 3 0 0 1 3 3v7.268a2 2 0 1 1-2 0zm-6 0a2 2 0 1 1-2 0V4.732a2 2 0 1 1 2 0v7.536zM4 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="group" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.048 11.997C-.377 11.975.013 11.782.013 10.56.013 9.235.653 8 4 8c.444 0 .84.022 1.194.062.164.435.426.82.76 1.132-1.786.389-2.721 1.353-2.906 2.803zm2.94-7.222a2.993 2.993 0 0 0-.976 1.95 2 2 0 1 1 .975-1.95zm6.964 7.222c-.185-1.45-1.12-2.414-2.906-2.803.334-.311.596-.697.76-1.132C11.16 8.022 11.556 8 12 8c3.346 0 3.987 1.235 3.987 2.56 0 1.222.39 1.415-3.035 1.437zm-1.964-5.272a2.993 2.993 0 0 0-.976-1.95 2 2 0 1 1 .976 1.95zM8 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 5c-2.177 0-3.987-.115-3.987-1.44S4.653 10 8 10c3.346 0 3.987 1.235 3.987 2.56S10.177 14 8 14z"/></symbol><symbol viewBox="0 0 16 16" id="history" xmlns="http://www.w3.org/2000/svg"><path d="M2.868 3.24a7 7 0 1 1-.043 9.475 1 1 0 0 1 1.478-1.348 5 5 0 1 0 .124-6.865l.796.645a.5.5 0 0 1-.193.873l-3.232.814a.5.5 0 0 1-.622-.504L1.3 3a.5.5 0 0 1 .814-.37l.754.61zM9 8h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V6a1 1 0 1 1 2 0v2z"/></symbol><symbol viewBox="0 0 16 16" id="home" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177a.505.505 0 0 1-.038.044l.038-.044zm-.787 0l.038.043a.5.5 0 0 1-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="hook" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-block" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.803 8a5.97 5.97 0 0 0-.462 1H4.5a.5.5 0 0 1 0-1h1.303zM4.5 5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm7.5.083a6.04 6.04 0 0 0-2 0V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.083a5.96 5.96 0 0 0 .72 2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v2.083zm1.121 3.796zM11 16a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm-1.293-2.292a3 3 0 0 0 4.001-4.001l-4.001 4zm-1.415-1.415l4.001-4a3 3 0 0 0-4.001 4.001z"/></symbol><symbol viewBox="0 0 16 16" id="issue-child" xmlns="http://www.w3.org/2000/svg"><path d="M11 8H5v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V7a.997.997 0 0 1 1-1h3V4H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v2h3a.997.997 0 0 1 1 1v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm-9 3v2h3v-2H2zm9 0v2h3v-2h-3z"/></symbol><symbol viewBox="0 0 16 16" id="issue-close" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M10.874 2H12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-2c-.918 0-1.74-.413-2.29-1.063a3.987 3.987 0 0 0 1.988-.984A1 1 0 0 0 10 14h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-1V3c0-.345-.044-.68-.126-1zM4 0h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-new" xmlns="http://www.w3.org/2000/svg"><path d="M10 2V1a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V4H9a1 1 0 1 1 0-2h1zm0 6a1 1 0 0 1 2 0v5a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h1a1 1 0 1 1 0 2H5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open-m" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-parent" xmlns="http://www.w3.org/2000/svg"><path d="M11 11H5v1h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H3v-2a.997.997 0 0 1 1-1h3V7H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9v2h3a.997.997 0 0 1 1 1v2h2.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H11v-1zM6 3v2h4V3H6z"/></symbol><symbol viewBox="0 0 16 16" id="issues" xmlns="http://www.w3.org/2000/svg"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7.575 6.689a4.002 4.002 0 0 1 6.274-4.86 4 4 0 0 1-4.86 6.274l-2.21 2.21.706.708a1 1 0 1 1-1.414 1.414l-.707-.707-.707.707.707.707a1 1 0 1 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.414-1.414l5.746-5.746zm2.032-.618a2 2 0 1 0 2.828-2.828A2 2 0 0 0 9.607 6.07z"/></symbol><symbol viewBox="0 0 16 16" id="key-2" xmlns="http://www.w3.org/2000/svg"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></symbol><symbol viewBox="0 0 16 16" id="label" xmlns="http://www.w3.org/2000/svg"><path d="M11.782 14.718a3 3 0 0 1-4.242 0L1.652 8.829a2 2 0 0 1-.565-1.702l.54-3.703a2 2 0 0 1 1.69-1.69l3.703-.54a2 2 0 0 1 1.703.564l5.888 5.888a3 3 0 0 1 0 4.243l-2.829 2.829zm1.415-5.657L7.309 3.173l-3.703.54-.54 3.702 5.888 5.888a1 1 0 0 0 1.414 0l2.829-2.828a1 1 0 0 0 0-1.414zM5.732 5.525A1 1 0 1 1 7.146 6.94a1 1 0 0 1-1.414-1.414z"/></symbol><symbol viewBox="0 0 16 16" id="labels" xmlns="http://www.w3.org/2000/svg"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667zM.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="leave" xmlns="http://www.w3.org/2000/svg"><path d="M11 7V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43V9H7a1 1 0 1 1 0-2h4zm-2 6.256a1 1 0 0 1 2 0A2.744 2.744 0 0 1 8.256 16H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5.19A2.81 2.81 0 0 1 11 2.81a1 1 0 0 1-2 0A.81.81 0 0 0 8.19 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.256c.41 0 .744-.333.744-.744z"/></symbol><symbol viewBox="0 0 16 16" id="level-up" xmlns="http://www.w3.org/2000/svg"><path fill="#2E2E2E" fill-rule="evenodd" d="M7 6h3.489a.5.5 0 0 0 .373-.832L6.374.117a.5.5 0 0 0-.748 0l-4.488 5.05A.5.5 0 0 0 1.51 6H5v7a3 3 0 0 0 3 3h6a1 1 0 0 0 0-2H8a1 1 0 0 1-1-1V6z"/></symbol><symbol viewBox="0 0 16 16" id="license" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.56 8.9l2.66 4.606a.3.3 0 0 1-.243.45l-1.678.094a.1.1 0 0 0-.078.044l-.953 1.432a.3.3 0 0 1-.51-.016L9.097 10.9a5.994 5.994 0 0 0 3.464-2zm-5.23 2.063L4.707 15.51a.3.3 0 0 1-.51.016l-.953-1.432a.1.1 0 0 0-.078-.044l-1.678-.094a.3.3 0 0 1-.243-.45l2.48-4.297a5.983 5.983 0 0 0 3.607 1.754zM8 10A5 5 0 1 1 8 0a5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="link" xmlns="http://www.w3.org/2000/svg"><path d="M6.986 3.35l2.12-2.122a4 4 0 0 1 5.657 5.657l-2.828 2.829a4 4 0 0 1-5.657 0 1 1 0 0 1 1.414-1.415 2 2 0 0 0 2.829 0l2.828-2.828a2 2 0 1 0-2.828-2.828l-1.001 1a5.018 5.018 0 0 0-2.534-.294zm2.12 9.192l-2.12 2.121a4 4 0 1 1-5.658-5.656l2.829-2.829a4 4 0 0 1 5.657 0 1 1 0 1 1-1.415 1.414 2 2 0 0 0-2.828 0l-2.828 2.829a2 2 0 1 0 2.828 2.828l1.001-1.001a5.018 5.018 0 0 0 2.534.294z"/></symbol><symbol viewBox="0 0 16 16" id="list-bulleted" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-7h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 5h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm-4 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-2h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="list-numbered" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 0 1 0-2zM1.156 5v-.828h.816V2.204h-.72v-.636c.432-.084.708-.192.996-.372h.756v2.976h.684V5H1.156zm-.18 5v-.588c.9-.828 1.596-1.464 1.596-1.98 0-.342-.192-.504-.468-.504-.252 0-.444.18-.624.36l-.552-.552c.396-.42.756-.612 1.32-.612.768 0 1.308.492 1.308 1.248 0 .612-.576 1.284-1.092 1.812.192-.024.468-.048.636-.048h.636V10H.976zm1.26 5.072c-.618 0-1.068-.204-1.356-.54l.468-.648c.234.216.51.36.78.36.336 0 .552-.12.552-.36 0-.288-.15-.456-.948-.456v-.72c.636 0 .828-.168.828-.432 0-.228-.138-.348-.396-.348-.252 0-.432.108-.672.312l-.516-.624c.372-.312.768-.492 1.236-.492.84 0 1.38.384 1.38 1.074 0 .366-.204.642-.612.822v.024c.432.132.732.432.732.912 0 .72-.684 1.116-1.476 1.116z"/></symbol><symbol viewBox="0 0 16 16" id="location" xmlns="http://www.w3.org/2000/svg"><path d="M8.755 15.144a1 1 0 0 1-1.51 0C3.748 11.114 2 8.065 2 6a6 6 0 1 1 12 0c0 2.065-1.748 5.113-5.245 9.144zM12 6a4 4 0 1 0-8 0c0 1.314 1.312 3.71 4 6.944C10.688 9.71 12 7.314 12 6zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="location-dot" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.314 13.087C4.382 13.295 3 13.85 3 14.5c0 .828 2.239 1.5 5 1.5s5-.672 5-1.5c0-.65-1.382-1.205-3.314-1.413l-.202.225a2 2 0 0 1-2.968 0l-.202-.225zm2.428-.445a1 1 0 0 1-1.484 0C4.419 9.5 3 7.037 3 5.252 3 2.353 5.239 0 8 0s5 2.352 5 5.253c0 1.784-1.42 4.247-4.258 7.389zM11 5.252C11 3.436 9.634 2 8 2S5 3.435 5 5.253c0 1.027.974 2.824 3 5.203 2.026-2.38 3-4.176 3-5.203zM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M10 5V4h2v1a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3V4h2v1h4zM4 7a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H4zm0-3a4 4 0 1 1 8 0h-2a2 2 0 1 0-4 0H4z"/></symbol><symbol viewBox="0 0 16 16" id="lock-open" xmlns="http://www.w3.org/2000/svg"><path d="M4.044 4a4 4 0 0 1 6.99-2.658 1 1 0 1 1-1.495 1.33A2 2 0 0 0 6.044 4a.998.998 0 0 1-.07.367v.701H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-5a3 3 0 0 1 2.974-3V4h.07zM4 7.07a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="log" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="mail" xmlns="http://www.w3.org/2000/svg"><path d="M14 5.6L9.338 9.796a2 2 0 0 1-2.676 0L2 5.6V11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5.6zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm.212 2L8 8.31 12.788 4H3.212z"/></symbol><symbol viewBox="0 0 16 16" id="merge-request-close" xmlns="http://www.w3.org/2000/svg"><path d="M9.414 8l1.414 1.414a1 1 0 1 1-1.414 1.414L8 9.414l-1.414 1.414a1 1 0 1 1-1.414-1.414L6.586 8 5.172 6.586a1 1 0 1 1 1.414-1.414L8 6.586l1.414-1.414a1 1 0 1 1 1.414 1.414L9.414 8zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="messages" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="mobile-issue-close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.657 10.728L2.12 7.192A1 1 0 1 0 .707 8.607l4.243 4.242a.997.997 0 0 0 1.414 0l8.485-8.485a1 1 0 1 0-1.414-1.414l-7.778 7.778z"/></symbol><symbol viewBox="0 0 16 16" id="monitor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></symbol><symbol viewBox="0 0 16 16" id="more" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="notifications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></symbol><symbol viewBox="0 0 16 16" id="notifications-off" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.26 5.089c.243.757.382 1.478.5 2.017.187.86.74 2.188 1.66 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0H4.35l2-2h7.29c-.993-1.937-1.6-3.396-1.835-4.468-.07-.326-.129-.59-.178-.81l1.634-1.633zM10.943 1.75l-1.48 1.48C9.07 3.076 8.612 3 8.069 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.065.317-.17.673-.317 1.073L.45 12.242a1.99 1.99 0 0 1 .224-1.19c.962-1.787 1.521-3.064 1.68-3.831.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024 4.867 4.867 0 0 1 1.944.688zm2.932-.105a1 1 0 0 1 0 1.415L2.561 14.374a1 1 0 1 1-1.415-1.414L12.46 1.646a1 1 0 0 1 1.414 0z"/></symbol><symbol viewBox="0 0 16 16" id="overview" xmlns="http://www.w3.org/2000/svg"><path d="M2 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2h-3zM2 9h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3h-3z"/></symbol><symbol viewBox="0 0 16 16" id="pencil" xmlns="http://www.w3.org/2000/svg"><path d="M13.02 1.293l1.414 1.414a1 1 0 0 1 0 1.414L4.119 14.436a1 1 0 0 1-.704.293l-2.407.008L1 12.316a1 1 0 0 1 .293-.71L11.605 1.292a1 1 0 0 1 1.414 0zm-1.416 1.415l-.707.707L12.31 4.83l.707-.707-1.414-1.415zM3.411 13.73l1.123-1.122H3.12v-1.415L2 12.312l.005 1.422 1.406-.005z"/></symbol><symbol viewBox="0 0 16 16" id="pipeline" xmlns="http://www.w3.org/2000/svg"><path d="M8.969 7.25a2 2 0 1 1-1.938 0A1.002 1.002 0 0 1 7 7V5.083a.2.2 0 0 1 .06-.142l.877-.87a.1.1 0 0 1 .141 0l.864.87A.2.2 0 0 1 9 5.083V7c0 .086-.01.17-.031.25zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm4.5-4a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="play" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.765 15.835c-.545.321-1.258.159-1.593-.363A1.075 1.075 0 0 1 1 14.89V1.11C1 .496 1.518 0 2.158 0c.214 0 .424.057.607.165l11.684 6.89c.544.321.714 1.005.38 1.526a1.135 1.135 0 0 1-.38.364l-11.684 6.89z"/></symbol><symbol viewBox="0 0 16 16" id="plus" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V1a1 1 0 1 1 2 0v6h6a1 1 0 0 1 0 2H9v6a1 1 0 0 1-2 0V9H1a1 1 0 1 1 0-2h6z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 7V4a1 1 0 1 0-2 0v3H4a1 1 0 1 0 0 2h3v3a1 1 0 0 0 2 0V9h3a1 1 0 0 0 0-2H9zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square-o" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="preferences" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 12h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2zm11-5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zM6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2z"/></symbol><symbol viewBox="0 0 16 16" id="profile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="push-rules" xmlns="http://www.w3.org/2000/svg"><path d="M6.268 9a2 2 0 0 1 3.464 0H11a1 1 0 0 1 0 2H9.732a2 2 0 0 1-3.464 0H5a1 1 0 0 1 0-2h1.268zM7 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-1v3.515a.3.3 0 0 1-.434.268l-1.432-.716a.3.3 0 0 0-.268 0l-1.432.716A.3.3 0 0 1 7 5.515V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm4 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="question" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm-1.46-5.602h2.233a3.97 3.97 0 0 1 .051-.558c.029-.17.073-.326.133-.469.06-.143.14-.28.242-.41.102-.13.228-.263.38-.399.26-.24.504-.467.733-.683a5.03 5.03 0 0 0 .598-.668c.17-.23.302-.477.399-.742a2.66 2.66 0 0 0 .144-.907c0-.505-.083-.95-.25-1.335a2.55 2.55 0 0 0-.723-.97 3.2 3.2 0 0 0-1.152-.589 5.441 5.441 0 0 0-1.531-.2c-.516 0-.998.063-1.445.188a3.19 3.19 0 0 0-1.168.59c-.331.268-.594.61-.79 1.027-.195.417-.295.917-.3 1.5h2.64c.006-.224.04-.416.102-.578.062-.161.142-.293.238-.394a.921.921 0 0 1 .332-.227 1.04 1.04 0 0 1 .39-.074c.34 0 .593.095.763.285.169.19.254.488.254.895 0 .328-.106.63-.317.906-.21.276-.499.565-.863.867-.214.182-.39.374-.531.574-.141.2-.253.42-.336.657a3.656 3.656 0 0 0-.176.777 7.89 7.89 0 0 0-.05.937zm-.321 2.375c0 .188.035.362.105.524.07.161.17.3.301.418.13.117.284.21.46.277.178.068.376.102.595.102.218 0 .416-.034.593-.102.178-.068.331-.16.461-.277a1.2 1.2 0 0 0 .301-.418c.07-.162.106-.336.106-.524a1.3 1.3 0 0 0-.106-.523 1.2 1.2 0 0 0-.3-.418 1.461 1.461 0 0 0-.462-.277 1.651 1.651 0 0 0-.593-.102c-.22 0-.417.034-.594.102a1.46 1.46 0 0 0-.461.277 1.2 1.2 0 0 0-.3.418 1.284 1.284 0 0 0-.106.523z"/></symbol><symbol viewBox="0 0 16 16" id="question-o" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-.778-4.151c0-.301.014-.575.044-.82a3.2 3.2 0 0 1 .154-.68c.073-.208.17-.4.294-.575.123-.176.278-.343.465-.503a4.81 4.81 0 0 0 .755-.758c.185-.242.277-.506.277-.793 0-.356-.074-.617-.222-.783-.148-.166-.37-.25-.667-.25a.92.92 0 0 0-.342.065.806.806 0 0 0-.29.199 1.04 1.04 0 0 0-.209.345 1.5 1.5 0 0 0-.088.506H5.082c.005-.51.092-.948.263-1.313.171-.364.401-.664.69-.899.29-.234.63-.406 1.023-.516a4.66 4.66 0 0 1 1.264-.164c.497 0 .944.058 1.34.174.397.117.733.289 1.008.517.276.227.487.51.633.847.146.337.218.727.218 1.17 0 .295-.042.56-.126.792a2.52 2.52 0 0 1-.349.65 4.4 4.4 0 0 1-.523.584c-.2.19-.414.389-.642.598a2.73 2.73 0 0 0-.332.349c-.089.114-.16.233-.212.359a1.868 1.868 0 0 0-.116.41 3.39 3.39 0 0 0-.044.489H7.222zm-.28 2.078c0-.164.03-.317.092-.458a1.05 1.05 0 0 1 .263-.366c.114-.103.248-.183.403-.243a1.45 1.45 0 0 1 .52-.089c.191 0 .364.03.52.09.154.059.289.14.403.242.114.103.201.224.263.366.061.141.092.294.092.458 0 .164-.03.316-.092.458a1.05 1.05 0 0 1-.263.365 1.278 1.278 0 0 1-.404.243 1.43 1.43 0 0 1-.52.089c-.19 0-.364-.03-.519-.089-.155-.06-.29-.14-.403-.243a1.05 1.05 0 0 1-.263-.365 1.135 1.135 0 0 1-.093-.458z"/></symbol><symbol viewBox="0 0 16 16" id="quote" xmlns="http://www.w3.org/2000/svg"><path d="M15 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1zM7 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="redo" xmlns="http://www.w3.org/2000/svg"><path d="M4.666 4.423a5 5 0 1 1-.203 6.944 1 1 0 1 0-1.478 1.347 7 7 0 1 0 .12-9.556L1.842 2.137a.5.5 0 0 0-.815.385L1 7.26a.5.5 0 0 0 .607.492l4.629-1.013a.5.5 0 0 0 .207-.877L4.666 4.423z"/></symbol><symbol viewBox="0 0 16 16" id="remove" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2v10a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V3zm3-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5zM4 3v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3H4zm2.5 2a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="repeat" xmlns="http://www.w3.org/2000/svg"><path d="M11.494 4.423a5 5 0 1 0 .203 6.944 1 1 0 1 1 1.478 1.347 7 7 0 1 1-.12-9.556l1.262-1.021a.5.5 0 0 1 .815.385l.028 4.738a.5.5 0 0 1-.607.492L9.924 6.739a.5.5 0 0 1-.207-.877l1.777-1.439z"/></symbol><symbol viewBox="0 0 16 16" id="retry" xmlns="http://www.w3.org/2000/svg"><path d="M4.009 6.958a4 4 0 0 0 5.283 4.775 1 1 0 0 1 .712 1.87A6 6 0 0 1 2.077 6.44l-.741-.2a.5.5 0 0 1-.12-.915L3.41 4.058a.5.5 0 0 1 .683.183l1.268 2.196a.5.5 0 0 1-.563.733l-.79-.212zm7.777 2.084a4 4 0 0 0-5.284-4.775 1 1 0 0 1-.711-1.87 6 6 0 0 1 7.927 7.162l.74.2a.5.5 0 0 1 .121.915l-2.196 1.268a.5.5 0 0 1-.683-.183l-1.267-2.196a.5.5 0 0 1 .562-.733l.79.212z"/></symbol><symbol viewBox="0 0 16 16" id="scale" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.99 9a.792.792 0 0 0-.078-.231L13 7l-.912 1.769a.791.791 0 0 0-.077.231h1.978zm-10 0a.792.792 0 0 0-.078-.231L3 7l-.912 1.769A.791.791 0 0 0 2.011 9h1.978zM2 0h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm3 14h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zM8 4a1 1 0 0 1 1 1v9H7V5a1 1 0 0 1 1-1zm-4.53-.714l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 3 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158L2.53 3.286a.53.53 0 0 1 .94 0zm10 0l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 13 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158l2.266-4.735a.53.53 0 0 1 .94 0z"/></symbol><symbol viewBox="0 0 16 16" id="screen-full" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14 14v-2a1 1 0 0 1 2 0v3a.997.997 0 0 1-1 1h-3a1 1 0 0 1 0-2h2zM2 14v-2a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1h3a1 1 0 0 0 0-2H2zM15.707.293A.997.997 0 0 1 16 1v3a1 1 0 0 1-2 0V2h-2a1 1 0 0 1 0-2h3c.276 0 .526.112.707.293zM2 2v2a1 1 0 1 1-2 0V1a.997.997 0 0 1 1-1h3a1 1 0 1 1 0 2H2zm4 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="screen-normal" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3V1a1 1 0 1 1 2 0v3a.997.997 0 0 1-1 1H1a1 1 0 1 1 0-2h2zm10 0h2a1 1 0 0 1 0 2h-3a.997.997 0 0 1-1-1V1a1 1 0 0 1 2 0v2zM3 13H1a1 1 0 0 1 0-2h3a.997.997 0 0 1 1 1v3a1 1 0 0 1-2 0v-2zm10 0v2a1 1 0 0 1-2 0v-3a.997.997 0 0 1 1-1h3a1 1 0 0 1 0 2h-2zM6.5 7h3a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="search" xmlns="http://www.w3.org/2000/svg"><path d="M8.853 8.854a3.5 3.5 0 1 0-4.95-4.95 3.5 3.5 0 0 0 4.95 4.95zm.207 2.328a5.5 5.5 0 1 1 2.121-2.121l3.329 3.328a1.5 1.5 0 0 1-2.121 2.121L9.06 11.182z"/></symbol><symbol viewBox="0 0 16 16" id="settings" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918a5.97 5.97 0 0 1 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="shield" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v7.186a3 3 0 0 1-1.426 2.554l-4 2.465a3 3 0 0 1-3.148 0l-4-2.465A3 3 0 0 1 1 10.186V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v7.186a1 1 0 0 0 .475.852l4 2.464a1 1 0 0 0 1.05 0l4-2.464a1 1 0 0 0 .475-.852V3a1 1 0 0 0-1-1H4zm0 1.5a.5.5 0 0 1 .5-.5h4v8.837a.5.5 0 0 1-.753.431l-3.5-2.052A.5.5 0 0 1 4 9.785V3.5z"/></symbol><symbol viewBox="0 0 16 16" id="slight-frown" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="slight-smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-5.163 2.254a.5.5 0 1 1 .865-.502 1.499 1.499 0 0 0 2.607-.018.5.5 0 1 1 .871.49 2.499 2.499 0 0 1-4.343.03z"/></symbol><symbol viewBox="0 0 16 16" id="smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6.18 6.27a.5.5 0 0 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zm6 0a.5.5 0 1 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zM5 9a3 3 0 0 0 6 0H5z"/></symbol><symbol viewBox="0 0 16 16" id="smiley" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5 9h6a3 3 0 0 1-6 0z"/></symbol><symbol viewBox="0 0 16 16" id="snippet" xmlns="http://www.w3.org/2000/svg"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 0 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 0 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></symbol><symbol viewBox="0 0 16 16" id="spam" xmlns="http://www.w3.org/2000/svg"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="star" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.609 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="star-o" xmlns="http://www.w3.org/2000/svg"><path d="M10.975 10.99a3 3 0 0 1 .655-2.083l1.54-1.916-2.219-.576a3 3 0 0 1-1.825-1.37L8 3.15 6.874 5.044a3 3 0 0 1-1.825 1.371l-2.218.576 1.54 1.916a3 3 0 0 1 .654 2.083l-.165 2.4 1.965-.836a3 3 0 0 1 2.348 0l1.965.836-.164-2.399zM7.61 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="stop" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="talic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 0h7a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm2 2h3L8 14H5L8 2zM3 14h7a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="task-done" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="template" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="thump-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 11h5.282a2 2 0 0 0 1.963-2.38l-.563-2.905a3 3 0 0 0-.243-.732l-1.103-2.286A3 3 0 0 0 10.964 1H7a3 3 0 0 0-3 3v6.3a2 2 0 0 0 .436 1.247l3.11 3.9a.632.632 0 0 0 .941.053l.137-.137a1 1 0 0 0 .28-.87L8.329 11zM1 10h2V3H1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="thump-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.103 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.487.5l.137.137a1 1 0 0 1 .28.87L8.329 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="timer" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 3.27l.77-.77a1 1 0 0 1 1.415 1.414l-.728.729a7 7 0 1 1-1.456-1.372zM8 14A5 5 0 1 0 8 4a5 5 0 0 0 0 10zm0-9a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zM6 0h4a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-add" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 4V2a1 1 0 0 1 2 0v2h2a1 1 0 0 1 0 2h-2v2a1 1 0 0 1-2 0V6H8a1 1 0 1 1 0-2h2zm2 7a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-done" xmlns="http://www.w3.org/2000/svg"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0L4.707 6.778a1 1 0 0 1 1.414-1.414l2.122 2.121zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="token" xmlns="http://www.w3.org/2000/svg"><path d="M3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm1 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="unapproval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.95 8.536l1.06-1.061a1 1 0 0 1 1.415 1.414l-1.061 1.06 1.06 1.061a1 1 0 0 1-1.414 1.415l-1.06-1.061-1.06 1.06a1 1 0 1 1-1.415-1.414l1.06-1.06-1.06-1.06a1 1 0 0 1 1.414-1.415l1.06 1.06zm-3.768-.33c.006.503.201 1.006.586 1.39l.353.354-.353.353a2 2 0 1 0 2.828 2.829l.354-.354.047.048C11.964 14.363 11.527 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.834 0 1.557.074 2.182.205zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="unassignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 5h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="unlink" xmlns="http://www.w3.org/2000/svg"><path d="M11.295 8.845l-.659-1.664a1.78 1.78 0 0 0 .04-.04l1.415-1.414c.586-.586.654-1.468.152-1.97s-1.384-.434-1.97.152L8.859 5.323a1.781 1.781 0 0 0-.04.04l-1.664-.658c.141-.208.305-.408.491-.594l1.415-1.414c1.366-1.367 3.424-1.525 4.596-.354 1.171 1.172 1.013 3.23-.354 4.596L11.89 8.354c-.186.186-.386.35-.594.491zm-2.45 2.45a4.075 4.075 0 0 1-.491.594l-1.415 1.414c-1.366 1.367-3.424 1.525-4.596.354-1.171-1.172-1.013-3.23.354-4.596L4.11 7.646c.186-.186.386-.35.594-.491l.659 1.664a1.781 1.781 0 0 0-.04.04l-1.415 1.414c-.586.586-.654 1.468-.152 1.97s1.384.434 1.97-.152l1.414-1.414a1.78 1.78 0 0 0 .04-.04l1.664.658zm3.812-2.088h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-.05a.5.5 0 0 1 .5-.5zm-.384 2.116l1.415 1.414a.5.5 0 0 1 0 .708l-.037.036a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 0-.707l.036-.037a.5.5 0 0 1 .707 0zm-2.823 1.09a.5.5 0 0 1 .5-.5h.052a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9.95a.5.5 0 0 1-.5-.5v-2zm-2.748-9.16a.5.5 0 0 1-.5.5h-.05a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h.05a.5.5 0 0 1 .5.5v2zm-2.116.383a.5.5 0 0 1 0 .707l-.036.036a.5.5 0 0 1-.707 0L2.428 2.965a.5.5 0 0 1 0-.707l.037-.036a.5.5 0 0 1 .707 0l1.414 1.414zm-1.09 2.823h-2a.5.5 0 0 1-.5-.5v-.051a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5z"/></symbol><symbol viewBox="0 0 16 16" id="user" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 8c-6.888 0-6.976-.78-6.976-2.52S2.144 8 8 8s6.976 2.692 6.976 4.48c0 1.788-.088 2.52-6.976 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="users" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="volume-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 5h1v6H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm2 0l4.445-2.964A1 1 0 0 1 9 2.87v10.26a1 1 0 0 1-1.555.833L3 11V5zm10.283 7.89a.5.5 0 0 1-.66-.752A5.485 5.485 0 0 0 14.5 8c0-1.601-.687-3.09-1.865-4.128a.5.5 0 0 1 .661-.75A6.484 6.484 0 0 1 15.5 8a6.485 6.485 0 0 1-2.217 4.89zm-2.002-2.236a.5.5 0 1 1-.652-.758c.55-.472.871-1.157.871-1.896 0-.732-.315-1.411-.856-1.883a.5.5 0 0 1 .658-.753A3.492 3.492 0 0 1 12.5 8c0 1.033-.45 1.994-1.219 2.654z"/></symbol><symbol viewBox="0 0 16 16" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M15.34 10.479A3 3 0 0 1 12.756 15h-9.51A3 3 0 0 1 .66 10.479l4.755-8.083a3 3 0 0 1 5.172 0l4.755 8.083zm-6.478-7.07a1 1 0 0 0-1.724 0l-4.755 8.084A1 1 0 0 0 3.245 13h9.51a1 1 0 0 0 .862-1.507L8.862 3.41zM8 5a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="work" xmlns="http://www.w3.org/2000/svg"><path d="M12 3h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h1V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zM6 2v1h4V2H6zM3 5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H3zm1.5 1a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm7 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol></svg> \ No newline at end of file
diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png
deleted file mode 100644
index f98ca15d787..00000000000
--- a/app/assets/images/new_nav.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/old_nav.png b/app/assets/images/old_nav.png
deleted file mode 100644
index 23fae7aa19e..00000000000
--- a/app/assets/images/old_nav.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/sprite.symbol.html b/app/assets/images/sprite.symbol.html
deleted file mode 100644
index d928d3f73b8..00000000000
--- a/app/assets/images/sprite.symbol.html
+++ /dev/null
@@ -1,3297 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
- <script src="https://rawgit.com/jonathantneal/svg4everybody/master/dist/svg4everybody.js"></script>
- <script>svg4everybody();</script>
- <title>SVG &lt;symbol&gt; sprite preview | svg-sprite</title>
- <style>@charset "UTF-8";body{padding:0;margin:0;color:#666;background:#fafafa;font-family:Arial,Helvetica,sans-serif;font-size:1em;line-height:1.4}header{display:block;padding:3em 3em 2em 3em;background-color:#fff}header p{margin:2em 0 0 0}section{border-top:1px solid #eee;padding:2em 3em 0 3em}section ul{margin:0;padding:0}section li{display:inline;display:inline-block;background-color:#fff;position:relative;margin:0 2em 2em 0;vertical-align:top;border:1px solid #ccc;padding:1em 1em 3em 1em;cursor:default}.icon-box{margin:0;width:144px;height:144px;position:relative;background:#ccc url("data:image/gif;base64,R0lGODlhDAAMAIAAAMzMzP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozQjk4OTI0MUY5NTIxMUUyQkJDMEI5NEFEM0Y1QTYwQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozQjk4OTI0MkY5NTIxMUUyQkJDMEI5NEFEM0Y1QTYwQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjNCOTg5MjNGRjk1MjExRTJCQkMwQjk0QUQzRjVBNjBDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjNCOTg5MjQwRjk1MjExRTJCQkMwQjk0QUQzRjVBNjBDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAAAwADAAAAhaEH6mHmmzcgzJAUG/NVGrfOZ8YLlABADs=") top left repeat;border:1px solid #ccc;display:table-cell;vertical-align:middle;text-align:center}.icon{display:inline;display:inline-block}h1{margin-top:0}h2{margin:0;padding:0;font-size:1em;font-weight:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:absolute;left:1em;right:1em;bottom:1em}footer{display:block;margin:0;padding:0 3em 3em 3em}footer p{margin:0;font-size:.7em}footer a{color:#0f7595;margin-left:0}</style>
-
-<!--
-
-Sprite shape dimensions
-====================================================================================================
-You will need to set the sprite shape dimensions via CSS when you use them as inline SVG, otherwise
-they would become a huge 100% in size. You may use the following dimension classes for doing so.
-They might well be outsourced to an external stylesheet of course.
-
--->
-
-<style type="text/css">
- .svg-abuse-dims { width: 16px; height: 16px; }
- .svg-account-dims { width: 16px; height: 16px; }
- .svg-admin-dims { width: 16px; height: 16px; }
- .svg-angle-double-left-dims { width: 16px; height: 16px; }
- .svg-angle-down-dims { width: 16px; height: 16px; }
- .svg-angle-left-dims { width: 16px; height: 16px; }
- .svg-angle-right-dims { width: 16px; height: 16px; }
- .svg-angle-up-dims { width: 16px; height: 16px; }
- .svg-appearance-dims { width: 16px; height: 16px; }
- .svg-applications-dims { width: 16px; height: 16px; }
- .svg-approval-dims { width: 16px; height: 16px; }
- .svg-arrow-right-dims { width: 16px; height: 16px; }
- .svg-assignee-dims { width: 16px; height: 16px; }
- .svg-bold-dims { width: 16px; height: 16px; }
- .svg-book-dims { width: 16px; height: 16px; }
- .svg-branch-dims { width: 16px; height: 16px; }
- .svg-calendar-dims { width: 16px; height: 16px; }
- .svg-cancel-dims { width: 16px; height: 16px; }
- .svg-chevron-down-dims { width: 16px; height: 16px; }
- .svg-chevron-left-dims { width: 16px; height: 16px; }
- .svg-chevron-right-dims { width: 16px; height: 16px; }
- .svg-chevron-up-dims { width: 16px; height: 16px; }
- .svg-clock-dims { width: 16px; height: 16px; }
- .svg-code-dims { width: 16px; height: 16px; }
- .svg-comment-dims { width: 16px; height: 16px; }
- .svg-comment-dots-dims { width: 16px; height: 16px; }
- .svg-comment-next-dims { width: 16px; height: 16px; }
- .svg-comments-dims { width: 16px; height: 16px; }
- .svg-commit-dims { width: 16px; height: 16px; }
- .svg-credit-card-dims { width: 16px; height: 16px; }
- .svg-disk-dims { width: 16px; height: 16px; }
- .svg-doc_code-dims { width: 16px; height: 16px; }
- .svg-doc_image-dims { width: 16px; height: 16px; }
- .svg-doc_text-dims { width: 16px; height: 16px; }
- .svg-download-dims { width: 16px; height: 16px; }
- .svg-duplicate-dims { width: 16px; height: 16px; }
- .svg-earth-dims { width: 16px; height: 16px; }
- .svg-eye-dims { width: 16px; height: 16px; }
- .svg-eye-slash-dims { width: 16px; height: 16px; }
- .svg-file-additions-dims { width: 16px; height: 16px; }
- .svg-file-deletion-dims { width: 16px; height: 16px; }
- .svg-file-modified-dims { width: 16px; height: 16px; }
- .svg-filter-dims { width: 16px; height: 16px; }
- .svg-folder-dims { width: 16px; height: 16px; }
- .svg-fork-dims { width: 16px; height: 16px; }
- .svg-git-merge-dims { width: 16px; height: 16px; }
- .svg-group-dims { width: 16px; height: 16px; }
- .svg-history-dims { width: 16px; height: 16px; }
- .svg-home-dims { width: 16px; height: 16px; }
- .svg-hook-dims { width: 16px; height: 16px; }
- .svg-issue-block-dims { width: 16px; height: 16px; }
- .svg-issue-child-dims { width: 16px; height: 16px; }
- .svg-issue-close-dims { width: 16px; height: 16px; }
- .svg-issue-duplicate-dims { width: 16px; height: 16px; }
- .svg-issue-new-dims { width: 16px; height: 16px; }
- .svg-issue-open-dims { width: 16px; height: 16px; }
- .svg-issue-open-m-dims { width: 16px; height: 16px; }
- .svg-issue-parent-dims { width: 16px; height: 16px; }
- .svg-issues-dims { width: 16px; height: 16px; }
- .svg-key-dims { width: 16px; height: 16px; }
- .svg-key-2-dims { width: 16px; height: 16px; }
- .svg-label-dims { width: 16px; height: 16px; }
- .svg-labels-dims { width: 16px; height: 16px; }
- .svg-leave-dims { width: 16px; height: 16px; }
- .svg-level-up-dims { width: 16px; height: 16px; }
- .svg-license-dims { width: 16px; height: 16px; }
- .svg-link-dims { width: 16px; height: 16px; }
- .svg-list-bulleted-dims { width: 16px; height: 16px; }
- .svg-list-numbered-dims { width: 16px; height: 16px; }
- .svg-location-dims { width: 16px; height: 16px; }
- .svg-location-dot-dims { width: 16px; height: 16px; }
- .svg-lock-dims { width: 16px; height: 16px; }
- .svg-lock-open-dims { width: 16px; height: 16px; }
- .svg-log-dims { width: 16px; height: 16px; }
- .svg-mail-dims { width: 16px; height: 16px; }
- .svg-merge-request-close-dims { width: 16px; height: 16px; }
- .svg-merge-request-close-m-dims { width: 16px; height: 16px; }
- .svg-messages-dims { width: 16px; height: 16px; }
- .svg-mobile-issue-close-dims { width: 16px; height: 16px; }
- .svg-monitor-dims { width: 16px; height: 16px; }
- .svg-more-dims { width: 16px; height: 16px; }
- .svg-notifications-dims { width: 16px; height: 16px; }
- .svg-notifications-off-dims { width: 16px; height: 16px; }
- .svg-overview-dims { width: 16px; height: 16px; }
- .svg-pencil-dims { width: 16px; height: 16px; }
- .svg-pipeline-dims { width: 16px; height: 16px; }
- .svg-play-dims { width: 16px; height: 16px; }
- .svg-plus-dims { width: 16px; height: 16px; }
- .svg-plus-square-dims { width: 16px; height: 16px; }
- .svg-plus-square-o-dims { width: 16px; height: 16px; }
- .svg-preferences-dims { width: 16px; height: 16px; }
- .svg-profile-dims { width: 16px; height: 16px; }
- .svg-project-dims { width: 16px; height: 16px; }
- .svg-push-rules-dims { width: 16px; height: 16px; }
- .svg-question-dims { width: 16px; height: 16px; }
- .svg-question-o-dims { width: 16px; height: 16px; }
- .svg-quote-dims { width: 16px; height: 16px; }
- .svg-redo-dims { width: 16px; height: 16px; }
- .svg-remove-dims { width: 16px; height: 16px; }
- .svg-repeat-dims { width: 16px; height: 16px; }
- .svg-retry-dims { width: 16px; height: 16px; }
- .svg-scale-dims { width: 16px; height: 16px; }
- .svg-screen-full-dims { width: 16px; height: 16px; }
- .svg-screen-normal-dims { width: 16px; height: 16px; }
- .svg-search-dims { width: 16px; height: 16px; }
- .svg-settings-dims { width: 16px; height: 16px; }
- .svg-shield-dims { width: 16px; height: 16px; }
- .svg-slight-frown-dims { width: 16px; height: 16px; }
- .svg-slight-smile-dims { width: 16px; height: 16px; }
- .svg-smile-dims { width: 16px; height: 16px; }
- .svg-smiley-dims { width: 16px; height: 16px; }
- .svg-snippet-dims { width: 16px; height: 16px; }
- .svg-spam-dims { width: 16px; height: 16px; }
- .svg-star-dims { width: 16px; height: 16px; }
- .svg-star-o-dims { width: 16px; height: 16px; }
- .svg-stop-dims { width: 16px; height: 16px; }
- .svg-talic-dims { width: 16px; height: 16px; }
- .svg-task-done-dims { width: 16px; height: 16px; }
- .svg-template-dims { width: 16px; height: 16px; }
- .svg-thump-down-dims { width: 16px; height: 16px; }
- .svg-thump-up-dims { width: 16px; height: 16px; }
- .svg-timer-dims { width: 16px; height: 16px; }
- .svg-todo-add-dims { width: 16px; height: 16px; }
- .svg-todo-done-dims { width: 16px; height: 16px; }
- .svg-token-dims { width: 16px; height: 16px; }
- .svg-unapproval-dims { width: 16px; height: 16px; }
- .svg-unassignee-dims { width: 16px; height: 16px; }
- .svg-unlink-dims { width: 16px; height: 16px; }
- .svg-user-dims { width: 16px; height: 16px; }
- .svg-users-dims { width: 16px; height: 16px; }
- .svg-volume-up-dims { width: 16px; height: 16px; }
- .svg-warning-dims { width: 16px; height: 16px; }
- .svg-work-dims { width: 16px; height: 16px; }
-</style>
-<!--
-====================================================================================================
--->
-
- </head>
- <body>
-
-<!--
-
-Inline <symbol> SVG sprite
-====================================================================================================
-This is an inlined version of the generated SVG sprite. The single images may be <use>d everywhere
-below within this document. Please see
-
- https://github.com/jkphl/svg-sprite/blob/master/docs/configuration.md#defs--symbol-mode
-
-for further details on how to create this embeddable sprite variant.
-
--->
-
-<svg width="0" height="0" style="position:absolute">
- <symbol viewBox="0 0 16 16" id="abuse" xmlns="http://www.w3.org/2000/svg"><path d="M11.408.328l4.029 3.222A1.5 1.5 0 0 1 16 4.72v6.555a1.5 1.5 0 0 1-.563 1.171l-4.026 3.224a1.5 1.5 0 0 1-.937.329H5.529a1.5 1.5 0 0 1-.937-.328L.563 12.45A1.5 1.5 0 0 1 0 11.28V4.724a1.5 1.5 0 0 1 .563-1.171L4.589.329A1.5 1.5 0 0 1 5.526 0h4.945c.34 0 .67.116.937.328zM10.296 2H5.702L2 4.964v6.074L5.704 14h4.594L14 11.036V4.962L10.296 2zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="account" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.195 9.965l-.568-.875a.25.25 0 0 1 .015-.294l.405-.5a.25.25 0 0 1 .283-.075l.938.36c.257-.183.543-.325.851-.42l.322-.988A.25.25 0 0 1 11.679 7h.642a.25.25 0 0 1 .238.173l.322.988c.308.095.594.237.851.42l.938-.36a.25.25 0 0 1 .283.076l.405.5a.25.25 0 0 1 .015.293l-.568.875c.113.297.18.616.193.95l.898.54a.25.25 0 0 1 .115.27l-.144.626a.25.25 0 0 1-.222.193l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.281a.25.25 0 0 1-.29-.05l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.904a.25.25 0 0 1-.289.051l-.577-.281a.25.25 0 0 1-.138-.26l.165-1.18a3.015 3.015 0 0 1-.512-.607l-1.115-.098a.25.25 0 0 1-.222-.193l-.144-.626a.25.25 0 0 1 .115-.27l.898-.54c.013-.334.08-.653.193-.95zM6.789 8.023A12.845 12.845 0 0 0 6 8c-5.036 0-6 2.74-6 4.48C0 14.22.076 15 6 15c.553 0 1.055-.006 1.51-.02A5.977 5.977 0 0 1 6 11c0-1.083.287-2.1.79-2.977zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM12 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="admin" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.162 2.5a3.5 3.5 0 0 1-3.163 5.479L6.08 14.766a1.5 1.5 0 0 1-2.598-1.5L7.4 6.479A3.5 3.5 0 0 1 10.564 1L8.9 3.88l2.599 1.5 1.663-2.88zm-8.63 11.949a.5.5 0 1 0 .5-.866.5.5 0 0 0-.5.866z"/></symbol>
- <symbol viewBox="0 0 16 16" id="angle-double-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.414 7.95l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 1 0 1.414-1.415L10.414 7.95zm-7 0l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 0 0 1.414-1.415L3.414 7.95z"/></symbol>
- <symbol viewBox="0 0 16 16" id="angle-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></symbol>
- <symbol viewBox="0 0 16 16" id="angle-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.757 8l4.95-4.95a1 1 0 1 0-1.414-1.414L3.636 7.293a.997.997 0 0 0 0 1.414l5.657 5.657a1 1 0 0 0 1.414-1.414L5.757 8z"/></symbol>
- <symbol viewBox="0 0 16 16" id="angle-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.243 8l-4.95-4.95a1 1 0 0 1 1.414-1.414l5.657 5.657a.997.997 0 0 1 0 1.414l-5.657 5.657a1 1 0 0 1-1.414-1.414L10.243 8z"/></symbol>
- <symbol viewBox="0 0 16 16" id="angle-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 6.757l-4.95 4.95a1 1 0 1 1-1.414-1.414l5.657-5.657a.997.997 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L8 6.757z"/></symbol>
- <symbol viewBox="0 0 16 16" id="appearance" xmlns="http://www.w3.org/2000/svg"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858a23.85 23.85 0 0 1-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023a9.7 9.7 0 0 0 .478-.035c-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="applications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="approval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.536 10.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 1 1 9.12 9.243l1.415 1.414zM7.632 8.109A2 2 0 0 0 7 11.364l2.121 2.121a1.996 1.996 0 0 0 2.807.021C11.686 14.554 10.627 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.6 0 1.142.038 1.632.109zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="assignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 5V4a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V7h-1a1 1 0 0 1 0-2h1zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol>
- <symbol viewBox="0 0 16 16" id="bold" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 15V1a1 1 0 0 1 1-1h4.604c.93 0 1.762.088 2.495.264.733.176 1.353.445 1.863.807.509.363.897.82 1.164 1.369.268.549.401 1.197.401 1.945 0 .366-.045.718-.137 1.055-.091.337-.23.652-.417.945a3.453 3.453 0 0 1-.71.796 3.645 3.645 0 0 1-1.021.588c.469.117.87.295 1.203.533.333.238.608.515.824.83.216.315.374.657.473 1.027.099.37.148.75.148 1.138 0 1.553-.5 2.725-1.5 3.516-1 .791-2.423 1.187-4.27 1.187H3a1 1 0 0 1-1-1zm3.297-5.967v4.319H8.12c.425 0 .791-.053 1.099-.16.307-.106.564-.252.769-.44.205-.186.357-.406.456-.659.099-.252.148-.529.148-.83a3.04 3.04 0 0 0-.131-.928 1.78 1.78 0 0 0-.413-.703 1.8 1.8 0 0 0-.73-.445c-.3-.103-.66-.154-1.077-.154H5.297zm0-2.33h2.44c.842-.014 1.468-.192 1.878-.533.41-.34.616-.826.616-1.456 0-.725-.21-1.247-.632-1.566-.421-.318-1.086-.478-1.995-.478H5.297v4.033z"/></symbol>
- <symbol viewBox="0 0 16 16" id="book" xmlns="http://www.w3.org/2000/svg"><path d="M7 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 7 6.191V2zM5 0h6a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="branch" xmlns="http://www.w3.org/2000/svg"><path d="M6 11.978v.29a2 2 0 1 1-2 0V3.732a2 2 0 1 1 2 0v3.849c.592-.491 1.31-.854 2.15-1.081 1.308-.353 1.875-.882 1.893-1.743a2 2 0 1 1 2.002-.051C12.053 6.54 10.857 7.84 8.67 8.43 7.056 8.867 6.195 9.98 6 11.978zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm6 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 15a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="calendar" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 2h2a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2h2V1a1 1 0 1 1 2 0v1h4V1a1 1 0 1 1 2 0v1zM0 4h16v9a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4zm2 2.5V13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5zM5 8h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="cancel" xmlns="http://www.w3.org/2000/svg"><path d="M3.11 4.523a6 6 0 0 0 8.367 8.367L3.109 4.524zM4.522 3.11l8.368 8.368A6 6 0 0 0 4.524 3.11zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol>
- <symbol viewBox="0 0 16 16" id="chevron-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.078 8.2l3.535-3.536a2 2 0 0 1 2.828 2.828l-4.949 4.95c-.39.39-.902.586-1.414.586a1.994 1.994 0 0 1-1.414-.586l-4.95-4.95a2 2 0 1 1 2.828-2.828l3.536 3.535z"/></symbol>
- <symbol viewBox="0 0 16 16" id="chevron-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.977 7.998l3.535-3.535a2 2 0 1 0-2.828-2.828l-4.95 4.949c-.39.39-.586.902-.586 1.414 0 .512.196 1.024.586 1.414l4.95 4.95a2 2 0 1 0 2.828-2.828L7.977 7.998z"/></symbol>
- <symbol viewBox="0 0 16 16" id="chevron-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.22 7.998L4.683 4.463a2 2 0 0 1 2.828-2.828l4.95 4.949c.39.39.586.902.586 1.414a1.99 1.99 0 0 1-.586 1.414l-4.95 4.95a2 2 0 0 1-2.828-2.828l3.535-3.536z"/></symbol>
- <symbol viewBox="0 0 16 16" id="chevron-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.778 8.957l3.535 3.535a2 2 0 1 0 2.828-2.828l-4.949-4.95a1.994 1.994 0 0 0-1.414-.586c-.512 0-1.024.196-1.414.586l-4.95 4.95a2 2 0 1 0 2.828 2.828l3.536-3.535z"/></symbol>
- <symbol viewBox="0 0 16 16" id="clock" xmlns="http://www.w3.org/2000/svg"><path d="M9 7h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V5a1 1 0 1 1 2 0v2zm-1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol>
- <symbol viewBox="0 0 16 16" id="code" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.871 8.243a.997.997 0 0 0-.293-.707L12.75 4.707a1 1 0 0 0-1.414 1.414l2.12 2.122-2.12 2.121a1 1 0 0 0 1.414 1.414l2.828-2.828a.997.997 0 0 0 .293-.707zm-13.243 0L4.75 6.12a1 1 0 1 0-1.414-1.414L.507 7.536a.997.997 0 0 0 0 1.414l2.829 2.828a1 1 0 1 0 1.414-1.414L2.628 8.243zm6.407-4.107a1 1 0 0 1 .707 1.225L8.19 11.157a1 1 0 1 1-1.931-.518L7.81 4.843a1 1 0 0 1 1.224-.707z"/></symbol>
- <symbol viewBox="0 0 16 16" id="comment" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol>
- <symbol viewBox="0 0 16 16" id="comment-dots" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586zM5 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="comment-next" xmlns="http://www.w3.org/2000/svg"><path d="M8 5V4a.5.5 0 0 1 .8-.4l2.667 2a.5.5 0 0 1 0 .8L8.8 8.4A.5.5 0 0 1 8 8V7H6a1 1 0 1 1 0-2h2zM1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol>
- <symbol viewBox="0 0 16 16" id="comments" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.75 10L0 13V3a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3.75zM13 5h1a2 2 0 0 1 2 2v8l-2.667-2H8a2 2 0 0 1-2-2h4a3 3 0 0 0 3-3V5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.876-1.008a4.002 4.002 0 0 1-7.752 0A1.01 1.01 0 0 1 4 9H1a1 1 0 1 1 0-2h3c.042 0 .083.003.124.008a4.002 4.002 0 0 1 7.752 0A1.01 1.01 0 0 1 12 7h3a1 1 0 0 1 0 2h-3a1.01 1.01 0 0 1-.124-.008z"/></symbol>
- <symbol viewBox="0 0 16 16" id="credit-card" xmlns="http://www.w3.org/2000/svg"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="disk" xmlns="http://www.w3.org/2000/svg"><path d="M16 11.764V3a3 3 0 0 0-3-3H3a3 3 0 0 0-3 3v8.764A2.989 2.989 0 0 1 2 11V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v8c.768 0 1.47.289 2 .764zM2 12h12a2 2 0 1 1 0 4H2a2 2 0 1 1 0-4zm10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="doc_code" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zm1.036 7.607a.498.498 0 0 1-.147.354l-1.414 1.414a.5.5 0 0 1-.707-.707l1.06-1.06-1.06-1.061a.5.5 0 0 1 .707-.707l1.414 1.414a.498.498 0 0 1 .147.353zm-4.822 0l1.06 1.061a.5.5 0 0 1-.706.707l-1.414-1.414a.498.498 0 0 1 0-.707l1.414-1.414a.5.5 0 1 1 .707.707l-1.06 1.06zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="doc_image" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM7.333 9.667l1.313-1.313a.5.5 0 0 1 .708 0L12 11H4l2.188-1.75a.5.5 0 0 1 .624 0l.521.417zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 8a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4 11h8v.7a.3.3 0 0 1-.3.3H4.3a.3.3 0 0 1-.3-.3V11z"/></symbol>
- <symbol viewBox="0 0 16 16" id="doc_text" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="download" xmlns="http://www.w3.org/2000/svg"><path d="M9 12h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0l-2-2.667A.5.5 0 0 1 6 12h1V8a1 1 0 1 1 2 0v4zM4 9a1 1 0 1 1 0 2 4 4 0 0 1-1.971-7.481 4 4 0 0 1 6.633-2.505 3.999 3.999 0 0 1 3.82 2.014A4 4 0 0 1 12 11a1 1 0 0 1 0-2 2 2 0 1 0 0-4h-1a2 2 0 0 0-3.112-1.662A2 2 0 1 0 4.268 5H4a2 2 0 1 0 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-3a1 1 0 0 1-1-1V6H8.527A.527.527 0 0 0 8 6.527V13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3zm-4-7H8.527c-.18 0-.355.013-.527.04V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2v2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3zM8.527 4h2.323a.5.5 0 0 1 .35.143l4.65 4.551a.5.5 0 0 1 .15.357V13a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6.527A2.527 2.527 0 0 1 8.527 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="earth" xmlns="http://www.w3.org/2000/svg"><path d="M8.7 2.04l-.082.177c.283.223.422.413.417.571-.008.237-.311.057-.444.274-.133.218.038.542-.112.637-.15.096-.398-.386-.479-.46-.054-.049-.166-.257-.336-.625l-.216-.225a.844.844 0 0 0-.418-.035c-.177.038-.075.1-.035.132.04.032.32.037.452.2.132.164.03.224-.05.298-.054.05-.157.062-.31.035H5.952l-.402.398.03.325.229.455.324-.463c.008-.206.058-.342.15-.41.14-.1.342-.15.534-.085.191.066-.057.218.011.271.068.053.204-.098.313-.02.11.08.07.155.104.322.036.167.254.114.398.328.144.215.19.29.147.483-.043.195-.168.26-.305.232-.138-.028-.107-.246-.275-.348-.168-.102-.266-.114-.386-.054-.12.06-.016.129.023.235.04.106.274.321.224.43-.05.107-.108.116-.42 0-.21-.077-.414-.007-.615.212l-.76.722c-.153.715-.3 1.13-.44 1.243-.211.17-.177-.483-.483-.656-.306-.174-.494-.047-.8-.07-.307-.023-.42.65-.38.873a.434.434 0 0 0 .221.321c.236-.141.39-.184.465-.128.11.084-.144.267-.074.425.07.158.314.069.386.283.073.213.084.48-.05.706-.135.227-.275.178-.4.053-.127-.126-.033-.375-.255-.704-.223-.329-.381-.337-.63-.787-.158-.287-.35-.743-.575-1.366a6 6 0 0 0 3.21 7.198l.001-.075c0-.577-.004-.944-.012-1.102-.011-.236-.95-.945-1.104-1.2-.154-.256-.34-.595-.355-.746-.016-.151.185-.232.344-.325.16-.093-.11-.367.028-.626.137-.258.395-.438.496-.356.101.081.058.228.267.333.209.104.077-.213.456-.178.38.035.143.201.252.216.11.016.113-.127.299-.143.186-.015.282.445.471.622.19.178.452.008.611.043.159.034.267.09.402.255.136.166-.03.352.073.557.103.205 1.07.22 1.433.255.364.034.371.011.371.324s-.166.314-.453.507c-.286.193-.166.462-.38.762-.212.3-.316.062-.622.14-.306.077-.413.382-.452.568-.039.186-.386.094-.877.232-.29.082-.429.144-.569.204a6.002 6.002 0 0 0 7.682-4.3c-.094-.384-.18-.63-.258-.74-.213-.297-.36.21-.924.49-.564.278-.57-.288-.81-.49-.16-.133-.212-.44-.158-.92-.005-.478.02-.828.077-1.049.057-.221.126-.543.207-.965.351-.373.606-.572.764-.595.237-.034.336.374.658.3a.315.315 0 0 0 .035-.01 5.993 5.993 0 0 0-.475-.824l-.309-.043a.646.646 0 0 0-.332-.117c-.205-.02-.025.128-.089.24-.064.112-.235.724-.437.685-.201-.039-.204-.374-.17-.668.036-.294-.077-.35-.2-.412-.124-.062-.325-.213-.556-.295-.232-.082-.123-.175-.093-.274.03-.1.208-.015.193-.058-.014-.044-.313-.135-.266-.167.03-.02.2-.02.506.003l.216-.012.293-.163a.58.58 0 0 0-.376-.22c-.233-.036-.513-.034-.73-.142-.205-.103-.458-.36-.643-.638A5.965 5.965 0 0 0 8.7 2.04zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol>
- <symbol viewBox="0 0 16 16" id="eye" xmlns="http://www.w3.org/2000/svg"><path d="M8 14C4.816 14 2.253 12.284.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2s5.747 1.716 7.607 5.019a2 2 0 0 1 0 1.962C13.747 12.284 11.184 14 8 14zm0-2c2.41 0 4.338-1.29 5.864-4C12.338 5.29 10.411 4 8 4 5.59 4 3.662 5.29 2.136 8 3.662 10.71 5.589 12 8 12zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm1-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M13.618 2.62L1.62 14.619a1 1 0 0 1-.985-1.668l1.525-1.526C1.516 10.742.926 9.927.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2c1.074 0 2.076.195 3.006.58l.944-.944a1 1 0 0 1 1.668.985zM8.068 11a3 3 0 0 0 2.931-2.932l-2.931 2.931zm-3.02-2.462a3 3 0 0 1 3.49-3.49l.884-.884A6.044 6.044 0 0 0 8 4C5.59 4 3.662 5.29 2.136 8c.445.79.924 1.46 1.439 2.011l1.473-1.473zm.421 5.06l1.658-1.658c.283.04.575.06.873.06 2.41 0 4.338-1.29 5.864-4a11.023 11.023 0 0 0-1.133-1.664l1.418-1.418a12.799 12.799 0 0 1 1.458 2.1 2 2 0 0 1 0 1.963C13.747 12.284 11.184 14 8 14a7.883 7.883 0 0 1-2.53-.402z"/></symbol>
- <symbol viewBox="0 0 16 16" id="file-additions" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="file-deletion" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm2 6h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="file-modified" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm5 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="filter" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 6v9l-3.724-1.862A.5.5 0 0 1 6 12.691V6L1.854 1.854A.5.5 0 0 1 2.207 1h11.586a.5.5 0 0 1 .353.854L10 6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="folder" xmlns="http://www.w3.org/2000/svg"><path d="M7.228 5l-.475-1.335A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H7.228zM13 3a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="fork" xmlns="http://www.w3.org/2000/svg"><path d="M9 12.268a2 2 0 1 1-2 0V8.874A4.002 4.002 0 0 1 4 5V3.732a2 2 0 1 1 2 0V5a2 2 0 1 0 4 0V3.732a2 2 0 1 1 2 0V5a4.002 4.002 0 0 1-3 3.874v3.394zM11 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="git-merge" xmlns="http://www.w3.org/2000/svg"><path d="M11 12.268V5a1 1 0 0 0-1-1v1a.5.5 0 0 1-.8.4l-2.667-2a.5.5 0 0 1 0-.8L9.2.6a.5.5 0 0 1 .8.4v1a3 3 0 0 1 3 3v7.268a2 2 0 1 1-2 0zm-6 0a2 2 0 1 1-2 0V4.732a2 2 0 1 1 2 0v7.536zM4 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="group" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.048 11.997C-.377 11.975.013 11.782.013 10.56.013 9.235.653 8 4 8c.444 0 .84.022 1.194.062.164.435.426.82.76 1.132-1.786.389-2.721 1.353-2.906 2.803zm2.94-7.222a2.993 2.993 0 0 0-.976 1.95 2 2 0 1 1 .975-1.95zm6.964 7.222c-.185-1.45-1.12-2.414-2.906-2.803.334-.311.596-.697.76-1.132C11.16 8.022 11.556 8 12 8c3.346 0 3.987 1.235 3.987 2.56 0 1.222.39 1.415-3.035 1.437zm-1.964-5.272a2.993 2.993 0 0 0-.976-1.95 2 2 0 1 1 .976 1.95zM8 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 5c-2.177 0-3.987-.115-3.987-1.44S4.653 10 8 10c3.346 0 3.987 1.235 3.987 2.56S10.177 14 8 14z"/></symbol>
- <symbol viewBox="0 0 16 16" id="history" xmlns="http://www.w3.org/2000/svg"><path d="M2.868 3.24a7 7 0 1 1-.043 9.475 1 1 0 0 1 1.478-1.348 5 5 0 1 0 .124-6.865l.796.645a.5.5 0 0 1-.193.873l-3.232.814a.5.5 0 0 1-.622-.504L1.3 3a.5.5 0 0 1 .814-.37l.754.61zM9 8h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V6a1 1 0 1 1 2 0v2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="home" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177a.505.505 0 0 1-.038.044l.038-.044zm-.787 0l.038.043a.5.5 0 0 1-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol>
- <symbol viewBox="0 0 16 16" id="hook" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-block" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.803 8a5.97 5.97 0 0 0-.462 1H4.5a.5.5 0 0 1 0-1h1.303zM4.5 5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm7.5.083a6.04 6.04 0 0 0-2 0V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.083a5.96 5.96 0 0 0 .72 2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v2.083zm1.121 3.796zM11 16a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm-1.293-2.292a3 3 0 0 0 4.001-4.001l-4.001 4zm-1.415-1.415l4.001-4a3 3 0 0 0-4.001 4.001z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-child" xmlns="http://www.w3.org/2000/svg"><path d="M11 8H5v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V7a.997.997 0 0 1 1-1h3V4H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v2h3a.997.997 0 0 1 1 1v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm-9 3v2h3v-2H2zm9 0v2h3v-2h-3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-close" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M10.874 2H12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-2c-.918 0-1.74-.413-2.29-1.063a3.987 3.987 0 0 0 1.988-.984A1 1 0 0 0 10 14h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-1V3c0-.345-.044-.68-.126-1zM4 0h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-new" xmlns="http://www.w3.org/2000/svg"><path d="M10 2V1a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V4H9a1 1 0 1 1 0-2h1zm0 6a1 1 0 0 1 2 0v5a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h1a1 1 0 1 1 0 2H5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V8z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-open" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-open-m" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issue-parent" xmlns="http://www.w3.org/2000/svg"><path d="M11 11H5v1h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H3v-2a.997.997 0 0 1 1-1h3V7H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9v2h3a.997.997 0 0 1 1 1v2h2.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H11v-1zM6 3v2h4V3H6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="issues" xmlns="http://www.w3.org/2000/svg"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7.575 6.689a4.002 4.002 0 0 1 6.274-4.86 4 4 0 0 1-4.86 6.274l-2.21 2.21.706.708a1 1 0 1 1-1.414 1.414l-.707-.707-.707.707.707.707a1 1 0 1 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.414-1.414l5.746-5.746zm2.032-.618a2 2 0 1 0 2.828-2.828A2 2 0 0 0 9.607 6.07z"/></symbol>
- <symbol viewBox="0 0 16 16" id="key-2" xmlns="http://www.w3.org/2000/svg"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></symbol>
- <symbol viewBox="0 0 16 16" id="label" xmlns="http://www.w3.org/2000/svg"><path d="M11.782 14.718a3 3 0 0 1-4.242 0L1.652 8.829a2 2 0 0 1-.565-1.702l.54-3.703a2 2 0 0 1 1.69-1.69l3.703-.54a2 2 0 0 1 1.703.564l5.888 5.888a3 3 0 0 1 0 4.243l-2.829 2.829zm1.415-5.657L7.309 3.173l-3.703.54-.54 3.702 5.888 5.888a1 1 0 0 0 1.414 0l2.829-2.828a1 1 0 0 0 0-1.414zM5.732 5.525A1 1 0 1 1 7.146 6.94a1 1 0 0 1-1.414-1.414z"/></symbol>
- <symbol viewBox="0 0 16 16" id="labels" xmlns="http://www.w3.org/2000/svg"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667zM.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="leave" xmlns="http://www.w3.org/2000/svg"><path d="M11 7V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43V9H7a1 1 0 1 1 0-2h4zm-2 6.256a1 1 0 0 1 2 0A2.744 2.744 0 0 1 8.256 16H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5.19A2.81 2.81 0 0 1 11 2.81a1 1 0 0 1-2 0A.81.81 0 0 0 8.19 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.256c.41 0 .744-.333.744-.744z"/></symbol>
- <symbol viewBox="0 0 16 16" id="level-up" xmlns="http://www.w3.org/2000/svg"><path fill="#2E2E2E" fill-rule="evenodd" d="M7 6h3.489a.5.5 0 0 0 .373-.832L6.374.117a.5.5 0 0 0-.748 0l-4.488 5.05A.5.5 0 0 0 1.51 6H5v7a3 3 0 0 0 3 3h6a1 1 0 0 0 0-2H8a1 1 0 0 1-1-1V6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="license" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.56 8.9l2.66 4.606a.3.3 0 0 1-.243.45l-1.678.094a.1.1 0 0 0-.078.044l-.953 1.432a.3.3 0 0 1-.51-.016L9.097 10.9a5.994 5.994 0 0 0 3.464-2zm-5.23 2.063L4.707 15.51a.3.3 0 0 1-.51.016l-.953-1.432a.1.1 0 0 0-.078-.044l-1.678-.094a.3.3 0 0 1-.243-.45l2.48-4.297a5.983 5.983 0 0 0 3.607 1.754zM8 10A5 5 0 1 1 8 0a5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="link" xmlns="http://www.w3.org/2000/svg"><path d="M6.986 3.35l2.12-2.122a4 4 0 0 1 5.657 5.657l-2.828 2.829a4 4 0 0 1-5.657 0 1 1 0 0 1 1.414-1.415 2 2 0 0 0 2.829 0l2.828-2.828a2 2 0 1 0-2.828-2.828l-1.001 1a5.018 5.018 0 0 0-2.534-.294zm2.12 9.192l-2.12 2.121a4 4 0 1 1-5.658-5.656l2.829-2.829a4 4 0 0 1 5.657 0 1 1 0 1 1-1.415 1.414 2 2 0 0 0-2.828 0l-2.828 2.829a2 2 0 1 0 2.828 2.828l1.001-1.001a5.018 5.018 0 0 0 2.534.294z"/></symbol>
- <symbol viewBox="0 0 16 16" id="list-bulleted" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-7h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 5h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm-4 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-2h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="list-numbered" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 0 1 0-2zM1.156 5v-.828h.816V2.204h-.72v-.636c.432-.084.708-.192.996-.372h.756v2.976h.684V5H1.156zm-.18 5v-.588c.9-.828 1.596-1.464 1.596-1.98 0-.342-.192-.504-.468-.504-.252 0-.444.18-.624.36l-.552-.552c.396-.42.756-.612 1.32-.612.768 0 1.308.492 1.308 1.248 0 .612-.576 1.284-1.092 1.812.192-.024.468-.048.636-.048h.636V10H.976zm1.26 5.072c-.618 0-1.068-.204-1.356-.54l.468-.648c.234.216.51.36.78.36.336 0 .552-.12.552-.36 0-.288-.15-.456-.948-.456v-.72c.636 0 .828-.168.828-.432 0-.228-.138-.348-.396-.348-.252 0-.432.108-.672.312l-.516-.624c.372-.312.768-.492 1.236-.492.84 0 1.38.384 1.38 1.074 0 .366-.204.642-.612.822v.024c.432.132.732.432.732.912 0 .72-.684 1.116-1.476 1.116z"/></symbol>
- <symbol viewBox="0 0 16 16" id="location" xmlns="http://www.w3.org/2000/svg"><path d="M8.755 15.144a1 1 0 0 1-1.51 0C3.748 11.114 2 8.065 2 6a6 6 0 1 1 12 0c0 2.065-1.748 5.113-5.245 9.144zM12 6a4 4 0 1 0-8 0c0 1.314 1.312 3.71 4 6.944C10.688 9.71 12 7.314 12 6zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="location-dot" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.314 13.087C4.382 13.295 3 13.85 3 14.5c0 .828 2.239 1.5 5 1.5s5-.672 5-1.5c0-.65-1.382-1.205-3.314-1.413l-.202.225a2 2 0 0 1-2.968 0l-.202-.225zm2.428-.445a1 1 0 0 1-1.484 0C4.419 9.5 3 7.037 3 5.252 3 2.353 5.239 0 8 0s5 2.352 5 5.253c0 1.784-1.42 4.247-4.258 7.389zM11 5.252C11 3.436 9.634 2 8 2S5 3.435 5 5.253c0 1.027.974 2.824 3 5.203 2.026-2.38 3-4.176 3-5.203zM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M10 5V4h2v1a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3V4h2v1h4zM4 7a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H4zm0-3a4 4 0 1 1 8 0h-2a2 2 0 1 0-4 0H4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="lock-open" xmlns="http://www.w3.org/2000/svg"><path d="M4.044 4a4 4 0 0 1 6.99-2.658 1 1 0 1 1-1.495 1.33A2 2 0 0 0 6.044 4a.998.998 0 0 1-.07.367v.701H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-5a3 3 0 0 1 2.974-3V4h.07zM4 7.07a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="log" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="mail" xmlns="http://www.w3.org/2000/svg"><path d="M14 5.6L9.338 9.796a2 2 0 0 1-2.676 0L2 5.6V11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5.6zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm.212 2L8 8.31 12.788 4H3.212z"/></symbol>
- <symbol viewBox="0 0 16 16" id="merge-request-close" xmlns="http://www.w3.org/2000/svg"><path d="M9.414 8l1.414 1.414a1 1 0 1 1-1.414 1.414L8 9.414l-1.414 1.414a1 1 0 1 1-1.414-1.414L6.586 8 5.172 6.586a1 1 0 1 1 1.414-1.414L8 6.586l1.414-1.414a1 1 0 1 1 1.414 1.414L9.414 8zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol>
- <symbol viewBox="0 0 16 16" id="merge-request-close-m" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.414 8l4.95-4.95a1 1 0 0 0-1.414-1.414L8 6.586l-4.95-4.95A1 1 0 0 0 1.636 3.05L6.586 8l-4.95 4.95a1 1 0 1 0 1.414 1.414L8 9.414l4.95 4.95a1 1 0 1 0 1.414-1.414L9.414 8z"/></symbol>
- <symbol viewBox="0 0 16 16" id="messages" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></symbol>
- <symbol viewBox="0 0 16 16" id="mobile-issue-close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.657 10.728L2.12 7.192A1 1 0 1 0 .707 8.607l4.243 4.242a.997.997 0 0 0 1.414 0l8.485-8.485a1 1 0 1 0-1.414-1.414l-7.778 7.778z"/></symbol>
- <symbol viewBox="0 0 16 16" id="monitor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></symbol>
- <symbol viewBox="0 0 16 16" id="more" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="notifications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></symbol>
- <symbol viewBox="0 0 16 16" id="notifications-off" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.26 5.089c.243.757.382 1.478.5 2.017.187.86.74 2.188 1.66 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0H4.35l2-2h7.29c-.993-1.937-1.6-3.396-1.835-4.468-.07-.326-.129-.59-.178-.81l1.634-1.633zM10.943 1.75l-1.48 1.48C9.07 3.076 8.612 3 8.069 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.065.317-.17.673-.317 1.073L.45 12.242a1.99 1.99 0 0 1 .224-1.19c.962-1.787 1.521-3.064 1.68-3.831.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024 4.867 4.867 0 0 1 1.944.688zm2.932-.105a1 1 0 0 1 0 1.415L2.561 14.374a1 1 0 1 1-1.415-1.414L12.46 1.646a1 1 0 0 1 1.414 0z"/></symbol>
- <symbol viewBox="0 0 16 16" id="overview" xmlns="http://www.w3.org/2000/svg"><path d="M2 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2h-3zM2 9h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3h-3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="pencil" xmlns="http://www.w3.org/2000/svg"><path d="M13.02 1.293l1.414 1.414a1 1 0 0 1 0 1.414L4.119 14.436a1 1 0 0 1-.704.293l-2.407.008L1 12.316a1 1 0 0 1 .293-.71L11.605 1.292a1 1 0 0 1 1.414 0zm-1.416 1.415l-.707.707L12.31 4.83l.707-.707-1.414-1.415zM3.411 13.73l1.123-1.122H3.12v-1.415L2 12.312l.005 1.422 1.406-.005z"/></symbol>
- <symbol viewBox="0 0 16 16" id="pipeline" xmlns="http://www.w3.org/2000/svg"><path d="M8.969 7.25a2 2 0 1 1-1.938 0A1.002 1.002 0 0 1 7 7V5.083a.2.2 0 0 1 .06-.142l.877-.87a.1.1 0 0 1 .141 0l.864.87A.2.2 0 0 1 9 5.083V7c0 .086-.01.17-.031.25zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm4.5-4a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="play" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.765 15.835c-.545.321-1.258.159-1.593-.363A1.075 1.075 0 0 1 1 14.89V1.11C1 .496 1.518 0 2.158 0c.214 0 .424.057.607.165l11.684 6.89c.544.321.714 1.005.38 1.526a1.135 1.135 0 0 1-.38.364l-11.684 6.89z"/></symbol>
- <symbol viewBox="0 0 16 16" id="plus" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V1a1 1 0 1 1 2 0v6h6a1 1 0 0 1 0 2H9v6a1 1 0 0 1-2 0V9H1a1 1 0 1 1 0-2h6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="plus-square" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 7V4a1 1 0 1 0-2 0v3H4a1 1 0 1 0 0 2h3v3a1 1 0 0 0 2 0V9h3a1 1 0 0 0 0-2H9zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="plus-square-o" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="preferences" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 12h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2zm11-5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zM6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="profile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol>
- <symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol>
- <symbol viewBox="0 0 16 16" id="push-rules" xmlns="http://www.w3.org/2000/svg"><path d="M6.268 9a2 2 0 0 1 3.464 0H11a1 1 0 0 1 0 2H9.732a2 2 0 0 1-3.464 0H5a1 1 0 0 1 0-2h1.268zM7 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-1v3.515a.3.3 0 0 1-.434.268l-1.432-.716a.3.3 0 0 0-.268 0l-1.432.716A.3.3 0 0 1 7 5.515V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm4 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="question" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm-1.46-5.602h2.233a3.97 3.97 0 0 1 .051-.558c.029-.17.073-.326.133-.469.06-.143.14-.28.242-.41.102-.13.228-.263.38-.399.26-.24.504-.467.733-.683a5.03 5.03 0 0 0 .598-.668c.17-.23.302-.477.399-.742a2.66 2.66 0 0 0 .144-.907c0-.505-.083-.95-.25-1.335a2.55 2.55 0 0 0-.723-.97 3.2 3.2 0 0 0-1.152-.589 5.441 5.441 0 0 0-1.531-.2c-.516 0-.998.063-1.445.188a3.19 3.19 0 0 0-1.168.59c-.331.268-.594.61-.79 1.027-.195.417-.295.917-.3 1.5h2.64c.006-.224.04-.416.102-.578.062-.161.142-.293.238-.394a.921.921 0 0 1 .332-.227 1.04 1.04 0 0 1 .39-.074c.34 0 .593.095.763.285.169.19.254.488.254.895 0 .328-.106.63-.317.906-.21.276-.499.565-.863.867-.214.182-.39.374-.531.574-.141.2-.253.42-.336.657a3.656 3.656 0 0 0-.176.777 7.89 7.89 0 0 0-.05.937zm-.321 2.375c0 .188.035.362.105.524.07.161.17.3.301.418.13.117.284.21.46.277.178.068.376.102.595.102.218 0 .416-.034.593-.102.178-.068.331-.16.461-.277a1.2 1.2 0 0 0 .301-.418c.07-.162.106-.336.106-.524a1.3 1.3 0 0 0-.106-.523 1.2 1.2 0 0 0-.3-.418 1.461 1.461 0 0 0-.462-.277 1.651 1.651 0 0 0-.593-.102c-.22 0-.417.034-.594.102a1.46 1.46 0 0 0-.461.277 1.2 1.2 0 0 0-.3.418 1.284 1.284 0 0 0-.106.523z"/></symbol>
- <symbol viewBox="0 0 16 16" id="question-o" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-.778-4.151c0-.301.014-.575.044-.82a3.2 3.2 0 0 1 .154-.68c.073-.208.17-.4.294-.575.123-.176.278-.343.465-.503a4.81 4.81 0 0 0 .755-.758c.185-.242.277-.506.277-.793 0-.356-.074-.617-.222-.783-.148-.166-.37-.25-.667-.25a.92.92 0 0 0-.342.065.806.806 0 0 0-.29.199 1.04 1.04 0 0 0-.209.345 1.5 1.5 0 0 0-.088.506H5.082c.005-.51.092-.948.263-1.313.171-.364.401-.664.69-.899.29-.234.63-.406 1.023-.516a4.66 4.66 0 0 1 1.264-.164c.497 0 .944.058 1.34.174.397.117.733.289 1.008.517.276.227.487.51.633.847.146.337.218.727.218 1.17 0 .295-.042.56-.126.792a2.52 2.52 0 0 1-.349.65 4.4 4.4 0 0 1-.523.584c-.2.19-.414.389-.642.598a2.73 2.73 0 0 0-.332.349c-.089.114-.16.233-.212.359a1.868 1.868 0 0 0-.116.41 3.39 3.39 0 0 0-.044.489H7.222zm-.28 2.078c0-.164.03-.317.092-.458a1.05 1.05 0 0 1 .263-.366c.114-.103.248-.183.403-.243a1.45 1.45 0 0 1 .52-.089c.191 0 .364.03.52.09.154.059.289.14.403.242.114.103.201.224.263.366.061.141.092.294.092.458 0 .164-.03.316-.092.458a1.05 1.05 0 0 1-.263.365 1.278 1.278 0 0 1-.404.243 1.43 1.43 0 0 1-.52.089c-.19 0-.364-.03-.519-.089-.155-.06-.29-.14-.403-.243a1.05 1.05 0 0 1-.263-.365 1.135 1.135 0 0 1-.093-.458z"/></symbol>
- <symbol viewBox="0 0 16 16" id="quote" xmlns="http://www.w3.org/2000/svg"><path d="M15 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1zM7 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="redo" xmlns="http://www.w3.org/2000/svg"><path d="M4.666 4.423a5 5 0 1 1-.203 6.944 1 1 0 1 0-1.478 1.347 7 7 0 1 0 .12-9.556L1.842 2.137a.5.5 0 0 0-.815.385L1 7.26a.5.5 0 0 0 .607.492l4.629-1.013a.5.5 0 0 0 .207-.877L4.666 4.423z"/></symbol>
- <symbol viewBox="0 0 16 16" id="remove" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2v10a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V3zm3-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5zM4 3v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3H4zm2.5 2a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="repeat" xmlns="http://www.w3.org/2000/svg"><path d="M11.494 4.423a5 5 0 1 0 .203 6.944 1 1 0 1 1 1.478 1.347 7 7 0 1 1-.12-9.556l1.262-1.021a.5.5 0 0 1 .815.385l.028 4.738a.5.5 0 0 1-.607.492L9.924 6.739a.5.5 0 0 1-.207-.877l1.777-1.439z"/></symbol>
- <symbol viewBox="0 0 16 16" id="retry" xmlns="http://www.w3.org/2000/svg"><path d="M4.009 6.958a4 4 0 0 0 5.283 4.775 1 1 0 0 1 .712 1.87A6 6 0 0 1 2.077 6.44l-.741-.2a.5.5 0 0 1-.12-.915L3.41 4.058a.5.5 0 0 1 .683.183l1.268 2.196a.5.5 0 0 1-.563.733l-.79-.212zm7.777 2.084a4 4 0 0 0-5.284-4.775 1 1 0 0 1-.711-1.87 6 6 0 0 1 7.927 7.162l.74.2a.5.5 0 0 1 .121.915l-2.196 1.268a.5.5 0 0 1-.683-.183l-1.267-2.196a.5.5 0 0 1 .562-.733l.79.212z"/></symbol>
- <symbol viewBox="0 0 16 16" id="scale" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.99 9a.792.792 0 0 0-.078-.231L13 7l-.912 1.769a.791.791 0 0 0-.077.231h1.978zm-10 0a.792.792 0 0 0-.078-.231L3 7l-.912 1.769A.791.791 0 0 0 2.011 9h1.978zM2 0h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm3 14h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zM8 4a1 1 0 0 1 1 1v9H7V5a1 1 0 0 1 1-1zm-4.53-.714l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 3 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158L2.53 3.286a.53.53 0 0 1 .94 0zm10 0l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 13 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158l2.266-4.735a.53.53 0 0 1 .94 0z"/></symbol>
- <symbol viewBox="0 0 16 16" id="screen-full" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14 14v-2a1 1 0 0 1 2 0v3a.997.997 0 0 1-1 1h-3a1 1 0 0 1 0-2h2zM2 14v-2a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1h3a1 1 0 0 0 0-2H2zM15.707.293A.997.997 0 0 1 16 1v3a1 1 0 0 1-2 0V2h-2a1 1 0 0 1 0-2h3c.276 0 .526.112.707.293zM2 2v2a1 1 0 1 1-2 0V1a.997.997 0 0 1 1-1h3a1 1 0 1 1 0 2H2zm4 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="screen-normal" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3V1a1 1 0 1 1 2 0v3a.997.997 0 0 1-1 1H1a1 1 0 1 1 0-2h2zm10 0h2a1 1 0 0 1 0 2h-3a.997.997 0 0 1-1-1V1a1 1 0 0 1 2 0v2zM3 13H1a1 1 0 0 1 0-2h3a.997.997 0 0 1 1 1v3a1 1 0 0 1-2 0v-2zm10 0v2a1 1 0 0 1-2 0v-3a.997.997 0 0 1 1-1h3a1 1 0 0 1 0 2h-2zM6.5 7h3a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="search" xmlns="http://www.w3.org/2000/svg"><path d="M8.853 8.854a3.5 3.5 0 1 0-4.95-4.95 3.5 3.5 0 0 0 4.95 4.95zm.207 2.328a5.5 5.5 0 1 1 2.121-2.121l3.329 3.328a1.5 1.5 0 0 1-2.121 2.121L9.06 11.182z"/></symbol>
- <symbol viewBox="0 0 16 16" id="settings" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918a5.97 5.97 0 0 1 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="shield" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v7.186a3 3 0 0 1-1.426 2.554l-4 2.465a3 3 0 0 1-3.148 0l-4-2.465A3 3 0 0 1 1 10.186V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v7.186a1 1 0 0 0 .475.852l4 2.464a1 1 0 0 0 1.05 0l4-2.464a1 1 0 0 0 .475-.852V3a1 1 0 0 0-1-1H4zm0 1.5a.5.5 0 0 1 .5-.5h4v8.837a.5.5 0 0 1-.753.431l-3.5-2.052A.5.5 0 0 1 4 9.785V3.5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="slight-frown" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="slight-smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-5.163 2.254a.5.5 0 1 1 .865-.502 1.499 1.499 0 0 0 2.607-.018.5.5 0 1 1 .871.49 2.499 2.499 0 0 1-4.343.03z"/></symbol>
- <symbol viewBox="0 0 16 16" id="smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6.18 6.27a.5.5 0 0 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zm6 0a.5.5 0 1 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zM5 9a3 3 0 0 0 6 0H5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="smiley" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5 9h6a3 3 0 0 1-6 0z"/></symbol>
- <symbol viewBox="0 0 16 16" id="snippet" xmlns="http://www.w3.org/2000/svg"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 0 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 0 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></symbol>
- <symbol viewBox="0 0 16 16" id="spam" xmlns="http://www.w3.org/2000/svg"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="star" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.609 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol>
- <symbol viewBox="0 0 16 16" id="star-o" xmlns="http://www.w3.org/2000/svg"><path d="M10.975 10.99a3 3 0 0 1 .655-2.083l1.54-1.916-2.219-.576a3 3 0 0 1-1.825-1.37L8 3.15 6.874 5.044a3 3 0 0 1-1.825 1.371l-2.218.576 1.54 1.916a3 3 0 0 1 .654 2.083l-.165 2.4 1.965-.836a3 3 0 0 1 2.348 0l1.965.836-.164-2.399zM7.61 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol>
- <symbol viewBox="0 0 16 16" id="stop" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="talic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 0h7a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm2 2h3L8 14H5L8 2zM3 14h7a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="task-done" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol>
- <symbol viewBox="0 0 16 16" id="template" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="thump-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 11h5.282a2 2 0 0 0 1.963-2.38l-.563-2.905a3 3 0 0 0-.243-.732l-1.103-2.286A3 3 0 0 0 10.964 1H7a3 3 0 0 0-3 3v6.3a2 2 0 0 0 .436 1.247l3.11 3.9a.632.632 0 0 0 .941.053l.137-.137a1 1 0 0 0 .28-.87L8.329 11zM1 10h2V3H1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="thump-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.103 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.487.5l.137.137a1 1 0 0 1 .28.87L8.329 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol>
- <symbol viewBox="0 0 16 16" id="timer" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 3.27l.77-.77a1 1 0 0 1 1.415 1.414l-.728.729a7 7 0 1 1-1.456-1.372zM8 14A5 5 0 1 0 8 4a5 5 0 0 0 0 10zm0-9a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zM6 0h4a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="todo-add" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 4V2a1 1 0 0 1 2 0v2h2a1 1 0 0 1 0 2h-2v2a1 1 0 0 1-2 0V6H8a1 1 0 1 1 0-2h2zm2 7a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="todo-done" xmlns="http://www.w3.org/2000/svg"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0L4.707 6.778a1 1 0 0 1 1.414-1.414l2.122 2.121zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="token" xmlns="http://www.w3.org/2000/svg"><path d="M3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm1 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="unapproval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.95 8.536l1.06-1.061a1 1 0 0 1 1.415 1.414l-1.061 1.06 1.06 1.061a1 1 0 0 1-1.414 1.415l-1.06-1.061-1.06 1.06a1 1 0 1 1-1.415-1.414l1.06-1.06-1.06-1.06a1 1 0 0 1 1.414-1.415l1.06 1.06zm-3.768-.33c.006.503.201 1.006.586 1.39l.353.354-.353.353a2 2 0 1 0 2.828 2.829l.354-.354.047.048C11.964 14.363 11.527 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.834 0 1.557.074 2.182.205zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol>
- <symbol viewBox="0 0 16 16" id="unassignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 5h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol>
- <symbol viewBox="0 0 16 16" id="unlink" xmlns="http://www.w3.org/2000/svg"><path d="M11.295 8.845l-.659-1.664a1.78 1.78 0 0 0 .04-.04l1.415-1.414c.586-.586.654-1.468.152-1.97s-1.384-.434-1.97.152L8.859 5.323a1.781 1.781 0 0 0-.04.04l-1.664-.658c.141-.208.305-.408.491-.594l1.415-1.414c1.366-1.367 3.424-1.525 4.596-.354 1.171 1.172 1.013 3.23-.354 4.596L11.89 8.354c-.186.186-.386.35-.594.491zm-2.45 2.45a4.075 4.075 0 0 1-.491.594l-1.415 1.414c-1.366 1.367-3.424 1.525-4.596.354-1.171-1.172-1.013-3.23.354-4.596L4.11 7.646c.186-.186.386-.35.594-.491l.659 1.664a1.781 1.781 0 0 0-.04.04l-1.415 1.414c-.586.586-.654 1.468-.152 1.97s1.384.434 1.97-.152l1.414-1.414a1.78 1.78 0 0 0 .04-.04l1.664.658zm3.812-2.088h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-.05a.5.5 0 0 1 .5-.5zm-.384 2.116l1.415 1.414a.5.5 0 0 1 0 .708l-.037.036a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 0-.707l.036-.037a.5.5 0 0 1 .707 0zm-2.823 1.09a.5.5 0 0 1 .5-.5h.052a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9.95a.5.5 0 0 1-.5-.5v-2zm-2.748-9.16a.5.5 0 0 1-.5.5h-.05a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h.05a.5.5 0 0 1 .5.5v2zm-2.116.383a.5.5 0 0 1 0 .707l-.036.036a.5.5 0 0 1-.707 0L2.428 2.965a.5.5 0 0 1 0-.707l.037-.036a.5.5 0 0 1 .707 0l1.414 1.414zm-1.09 2.823h-2a.5.5 0 0 1-.5-.5v-.051a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5z"/></symbol>
- <symbol viewBox="0 0 16 16" id="user" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 8c-6.888 0-6.976-.78-6.976-2.52S2.144 8 8 8s6.976 2.692 6.976 4.48c0 1.788-.088 2.52-6.976 2.52z"/></symbol>
- <symbol viewBox="0 0 16 16" id="users" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol>
- <symbol viewBox="0 0 16 16" id="volume-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 5h1v6H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm2 0l4.445-2.964A1 1 0 0 1 9 2.87v10.26a1 1 0 0 1-1.555.833L3 11V5zm10.283 7.89a.5.5 0 0 1-.66-.752A5.485 5.485 0 0 0 14.5 8c0-1.601-.687-3.09-1.865-4.128a.5.5 0 0 1 .661-.75A6.484 6.484 0 0 1 15.5 8a6.485 6.485 0 0 1-2.217 4.89zm-2.002-2.236a.5.5 0 1 1-.652-.758c.55-.472.871-1.157.871-1.896 0-.732-.315-1.411-.856-1.883a.5.5 0 0 1 .658-.753A3.492 3.492 0 0 1 12.5 8c0 1.033-.45 1.994-1.219 2.654z"/></symbol>
- <symbol viewBox="0 0 16 16" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M15.34 10.479A3 3 0 0 1 12.756 15h-9.51A3 3 0 0 1 .66 10.479l4.755-8.083a3 3 0 0 1 5.172 0l4.755 8.083zm-6.478-7.07a1 1 0 0 0-1.724 0l-4.755 8.084A1 1 0 0 0 3.245 13h9.51a1 1 0 0 0 .862-1.507L8.862 3.41zM8 5a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol>
- <symbol viewBox="0 0 16 16" id="work" xmlns="http://www.w3.org/2000/svg"><path d="M12 3h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h1V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zM6 2v1h4V2H6zM3 5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H3zm1.5 1a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm7 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol>
-</svg>
-
-<!--
-====================================================================================================
--->
-
- <header>
- <h1>SVG <code>&lt;symbol&gt;</code> sprite preview</h1>
- <p>This preview features two methods of using the generated sprite in conjunction with inline SVG. Please have a look at the HTML source for further details and be aware of the following constraints:</p>
- <ul>
- <li>Your browser has to <a href="http://caniuse.com/#feat=svg-html5" target="_blank">support inline SVG</a> for these techniques to work.</li>
- <li>The embedded sprite (A) slightly differs from the generated external one. Please <a href="https://github.com/jkphl/svg-sprite/blob/master/docs/configuration.md#defs--symbol-mode" target="_blank">see the documentation</a> for details on how to create such an embeddable sprite.</li>
- <li>Internet Explorer up to version 11 doesn't support external sprites for use with inline SVG. For IE 9-11, you may polyfill this functionality with <a href="https://github.com/jonathantneal/svg4everybody" target="_blank">SVG for Everybody</a>.</li>
- </ul>
- </header>
- <section>
-
-<!--
-
-A) Inline SVG with embedded sprite
-====================================================================================================
-These SVG images make use of fragment identifiers (IDs) and are extracted out of the inline sprite
-embedded above. They may be styled via CSS.
-
--->
-
- <h3>A) Inline SVG with embedded sprite</h3>
- <ul>
-
- <li title="abuse">
- <div class="icon-box">
-
- <!-- abuse -->
- <svg class="svg-abuse-dims">
- <use xlink:href="#abuse"></use>
- </svg>
-
- </div>
- <h2>abuse</h2>
- </li>
- <li title="account">
- <div class="icon-box">
-
- <!-- account -->
- <svg class="svg-account-dims">
- <use xlink:href="#account"></use>
- </svg>
-
- </div>
- <h2>account</h2>
- </li>
- <li title="admin">
- <div class="icon-box">
-
- <!-- admin -->
- <svg class="svg-admin-dims">
- <use xlink:href="#admin"></use>
- </svg>
-
- </div>
- <h2>admin</h2>
- </li>
- <li title="angle-double-left">
- <div class="icon-box">
-
- <!-- angle-double-left -->
- <svg class="svg-angle-double-left-dims">
- <use xlink:href="#angle-double-left"></use>
- </svg>
-
- </div>
- <h2>angle-double-left</h2>
- </li>
- <li title="angle-down">
- <div class="icon-box">
-
- <!-- angle-down -->
- <svg class="svg-angle-down-dims">
- <use xlink:href="#angle-down"></use>
- </svg>
-
- </div>
- <h2>angle-down</h2>
- </li>
- <li title="angle-left">
- <div class="icon-box">
-
- <!-- angle-left -->
- <svg class="svg-angle-left-dims">
- <use xlink:href="#angle-left"></use>
- </svg>
-
- </div>
- <h2>angle-left</h2>
- </li>
- <li title="angle-right">
- <div class="icon-box">
-
- <!-- angle-right -->
- <svg class="svg-angle-right-dims">
- <use xlink:href="#angle-right"></use>
- </svg>
-
- </div>
- <h2>angle-right</h2>
- </li>
- <li title="angle-up">
- <div class="icon-box">
-
- <!-- angle-up -->
- <svg class="svg-angle-up-dims">
- <use xlink:href="#angle-up"></use>
- </svg>
-
- </div>
- <h2>angle-up</h2>
- </li>
- <li title="appearance">
- <div class="icon-box">
-
- <!-- appearance -->
- <svg class="svg-appearance-dims">
- <use xlink:href="#appearance"></use>
- </svg>
-
- </div>
- <h2>appearance</h2>
- </li>
- <li title="applications">
- <div class="icon-box">
-
- <!-- applications -->
- <svg class="svg-applications-dims">
- <use xlink:href="#applications"></use>
- </svg>
-
- </div>
- <h2>applications</h2>
- </li>
- <li title="approval">
- <div class="icon-box">
-
- <!-- approval -->
- <svg class="svg-approval-dims">
- <use xlink:href="#approval"></use>
- </svg>
-
- </div>
- <h2>approval</h2>
- </li>
- <li title="arrow-right">
- <div class="icon-box">
-
- <!-- arrow-right -->
- <svg class="svg-arrow-right-dims">
- <use xlink:href="#arrow-right"></use>
- </svg>
-
- </div>
- <h2>arrow-right</h2>
- </li>
- <li title="assignee">
- <div class="icon-box">
-
- <!-- assignee -->
- <svg class="svg-assignee-dims">
- <use xlink:href="#assignee"></use>
- </svg>
-
- </div>
- <h2>assignee</h2>
- </li>
- <li title="bold">
- <div class="icon-box">
-
- <!-- bold -->
- <svg class="svg-bold-dims">
- <use xlink:href="#bold"></use>
- </svg>
-
- </div>
- <h2>bold</h2>
- </li>
- <li title="book">
- <div class="icon-box">
-
- <!-- book -->
- <svg class="svg-book-dims">
- <use xlink:href="#book"></use>
- </svg>
-
- </div>
- <h2>book</h2>
- </li>
- <li title="branch">
- <div class="icon-box">
-
- <!-- branch -->
- <svg class="svg-branch-dims">
- <use xlink:href="#branch"></use>
- </svg>
-
- </div>
- <h2>branch</h2>
- </li>
- <li title="calendar">
- <div class="icon-box">
-
- <!-- calendar -->
- <svg class="svg-calendar-dims">
- <use xlink:href="#calendar"></use>
- </svg>
-
- </div>
- <h2>calendar</h2>
- </li>
- <li title="cancel">
- <div class="icon-box">
-
- <!-- cancel -->
- <svg class="svg-cancel-dims">
- <use xlink:href="#cancel"></use>
- </svg>
-
- </div>
- <h2>cancel</h2>
- </li>
- <li title="chevron-down">
- <div class="icon-box">
-
- <!-- chevron-down -->
- <svg class="svg-chevron-down-dims">
- <use xlink:href="#chevron-down"></use>
- </svg>
-
- </div>
- <h2>chevron-down</h2>
- </li>
- <li title="chevron-left">
- <div class="icon-box">
-
- <!-- chevron-left -->
- <svg class="svg-chevron-left-dims">
- <use xlink:href="#chevron-left"></use>
- </svg>
-
- </div>
- <h2>chevron-left</h2>
- </li>
- <li title="chevron-right">
- <div class="icon-box">
-
- <!-- chevron-right -->
- <svg class="svg-chevron-right-dims">
- <use xlink:href="#chevron-right"></use>
- </svg>
-
- </div>
- <h2>chevron-right</h2>
- </li>
- <li title="chevron-up">
- <div class="icon-box">
-
- <!-- chevron-up -->
- <svg class="svg-chevron-up-dims">
- <use xlink:href="#chevron-up"></use>
- </svg>
-
- </div>
- <h2>chevron-up</h2>
- </li>
- <li title="clock">
- <div class="icon-box">
-
- <!-- clock -->
- <svg class="svg-clock-dims">
- <use xlink:href="#clock"></use>
- </svg>
-
- </div>
- <h2>clock</h2>
- </li>
- <li title="code">
- <div class="icon-box">
-
- <!-- code -->
- <svg class="svg-code-dims">
- <use xlink:href="#code"></use>
- </svg>
-
- </div>
- <h2>code</h2>
- </li>
- <li title="comment">
- <div class="icon-box">
-
- <!-- comment -->
- <svg class="svg-comment-dims">
- <use xlink:href="#comment"></use>
- </svg>
-
- </div>
- <h2>comment</h2>
- </li>
- <li title="comment-dots">
- <div class="icon-box">
-
- <!-- comment-dots -->
- <svg class="svg-comment-dots-dims">
- <use xlink:href="#comment-dots"></use>
- </svg>
-
- </div>
- <h2>comment-dots</h2>
- </li>
- <li title="comment-next">
- <div class="icon-box">
-
- <!-- comment-next -->
- <svg class="svg-comment-next-dims">
- <use xlink:href="#comment-next"></use>
- </svg>
-
- </div>
- <h2>comment-next</h2>
- </li>
- <li title="comments">
- <div class="icon-box">
-
- <!-- comments -->
- <svg class="svg-comments-dims">
- <use xlink:href="#comments"></use>
- </svg>
-
- </div>
- <h2>comments</h2>
- </li>
- <li title="commit">
- <div class="icon-box">
-
- <!-- commit -->
- <svg class="svg-commit-dims">
- <use xlink:href="#commit"></use>
- </svg>
-
- </div>
- <h2>commit</h2>
- </li>
- <li title="credit-card">
- <div class="icon-box">
-
- <!-- credit-card -->
- <svg class="svg-credit-card-dims">
- <use xlink:href="#credit-card"></use>
- </svg>
-
- </div>
- <h2>credit-card</h2>
- </li>
- <li title="disk">
- <div class="icon-box">
-
- <!-- disk -->
- <svg class="svg-disk-dims">
- <use xlink:href="#disk"></use>
- </svg>
-
- </div>
- <h2>disk</h2>
- </li>
- <li title="doc_code">
- <div class="icon-box">
-
- <!-- doc_code -->
- <svg class="svg-doc_code-dims">
- <use xlink:href="#doc_code"></use>
- </svg>
-
- </div>
- <h2>doc_code</h2>
- </li>
- <li title="doc_image">
- <div class="icon-box">
-
- <!-- doc_image -->
- <svg class="svg-doc_image-dims">
- <use xlink:href="#doc_image"></use>
- </svg>
-
- </div>
- <h2>doc_image</h2>
- </li>
- <li title="doc_text">
- <div class="icon-box">
-
- <!-- doc_text -->
- <svg class="svg-doc_text-dims">
- <use xlink:href="#doc_text"></use>
- </svg>
-
- </div>
- <h2>doc_text</h2>
- </li>
- <li title="download">
- <div class="icon-box">
-
- <!-- download -->
- <svg class="svg-download-dims">
- <use xlink:href="#download"></use>
- </svg>
-
- </div>
- <h2>download</h2>
- </li>
- <li title="duplicate">
- <div class="icon-box">
-
- <!-- duplicate -->
- <svg class="svg-duplicate-dims">
- <use xlink:href="#duplicate"></use>
- </svg>
-
- </div>
- <h2>duplicate</h2>
- </li>
- <li title="earth">
- <div class="icon-box">
-
- <!-- earth -->
- <svg class="svg-earth-dims">
- <use xlink:href="#earth"></use>
- </svg>
-
- </div>
- <h2>earth</h2>
- </li>
- <li title="eye">
- <div class="icon-box">
-
- <!-- eye -->
- <svg class="svg-eye-dims">
- <use xlink:href="#eye"></use>
- </svg>
-
- </div>
- <h2>eye</h2>
- </li>
- <li title="eye-slash">
- <div class="icon-box">
-
- <!-- eye-slash -->
- <svg class="svg-eye-slash-dims">
- <use xlink:href="#eye-slash"></use>
- </svg>
-
- </div>
- <h2>eye-slash</h2>
- </li>
- <li title="file-additions">
- <div class="icon-box">
-
- <!-- file-additions -->
- <svg class="svg-file-additions-dims">
- <use xlink:href="#file-additions"></use>
- </svg>
-
- </div>
- <h2>file-additions</h2>
- </li>
- <li title="file-deletion">
- <div class="icon-box">
-
- <!-- file-deletion -->
- <svg class="svg-file-deletion-dims">
- <use xlink:href="#file-deletion"></use>
- </svg>
-
- </div>
- <h2>file-deletion</h2>
- </li>
- <li title="file-modified">
- <div class="icon-box">
-
- <!-- file-modified -->
- <svg class="svg-file-modified-dims">
- <use xlink:href="#file-modified"></use>
- </svg>
-
- </div>
- <h2>file-modified</h2>
- </li>
- <li title="filter">
- <div class="icon-box">
-
- <!-- filter -->
- <svg class="svg-filter-dims">
- <use xlink:href="#filter"></use>
- </svg>
-
- </div>
- <h2>filter</h2>
- </li>
- <li title="folder">
- <div class="icon-box">
-
- <!-- folder -->
- <svg class="svg-folder-dims">
- <use xlink:href="#folder"></use>
- </svg>
-
- </div>
- <h2>folder</h2>
- </li>
- <li title="fork">
- <div class="icon-box">
-
- <!-- fork -->
- <svg class="svg-fork-dims">
- <use xlink:href="#fork"></use>
- </svg>
-
- </div>
- <h2>fork</h2>
- </li>
- <li title="git-merge">
- <div class="icon-box">
-
- <!-- git-merge -->
- <svg class="svg-git-merge-dims">
- <use xlink:href="#git-merge"></use>
- </svg>
-
- </div>
- <h2>git-merge</h2>
- </li>
- <li title="group">
- <div class="icon-box">
-
- <!-- group -->
- <svg class="svg-group-dims">
- <use xlink:href="#group"></use>
- </svg>
-
- </div>
- <h2>group</h2>
- </li>
- <li title="history">
- <div class="icon-box">
-
- <!-- history -->
- <svg class="svg-history-dims">
- <use xlink:href="#history"></use>
- </svg>
-
- </div>
- <h2>history</h2>
- </li>
- <li title="home">
- <div class="icon-box">
-
- <!-- home -->
- <svg class="svg-home-dims">
- <use xlink:href="#home"></use>
- </svg>
-
- </div>
- <h2>home</h2>
- </li>
- <li title="hook">
- <div class="icon-box">
-
- <!-- hook -->
- <svg class="svg-hook-dims">
- <use xlink:href="#hook"></use>
- </svg>
-
- </div>
- <h2>hook</h2>
- </li>
- <li title="issue-block">
- <div class="icon-box">
-
- <!-- issue-block -->
- <svg class="svg-issue-block-dims">
- <use xlink:href="#issue-block"></use>
- </svg>
-
- </div>
- <h2>issue-block</h2>
- </li>
- <li title="issue-child">
- <div class="icon-box">
-
- <!-- issue-child -->
- <svg class="svg-issue-child-dims">
- <use xlink:href="#issue-child"></use>
- </svg>
-
- </div>
- <h2>issue-child</h2>
- </li>
- <li title="issue-close">
- <div class="icon-box">
-
- <!-- issue-close -->
- <svg class="svg-issue-close-dims">
- <use xlink:href="#issue-close"></use>
- </svg>
-
- </div>
- <h2>issue-close</h2>
- </li>
- <li title="issue-duplicate">
- <div class="icon-box">
-
- <!-- issue-duplicate -->
- <svg class="svg-issue-duplicate-dims">
- <use xlink:href="#issue-duplicate"></use>
- </svg>
-
- </div>
- <h2>issue-duplicate</h2>
- </li>
- <li title="issue-new">
- <div class="icon-box">
-
- <!-- issue-new -->
- <svg class="svg-issue-new-dims">
- <use xlink:href="#issue-new"></use>
- </svg>
-
- </div>
- <h2>issue-new</h2>
- </li>
- <li title="issue-open">
- <div class="icon-box">
-
- <!-- issue-open -->
- <svg class="svg-issue-open-dims">
- <use xlink:href="#issue-open"></use>
- </svg>
-
- </div>
- <h2>issue-open</h2>
- </li>
- <li title="issue-open-m">
- <div class="icon-box">
-
- <!-- issue-open-m -->
- <svg class="svg-issue-open-m-dims">
- <use xlink:href="#issue-open-m"></use>
- </svg>
-
- </div>
- <h2>issue-open-m</h2>
- </li>
- <li title="issue-parent">
- <div class="icon-box">
-
- <!-- issue-parent -->
- <svg class="svg-issue-parent-dims">
- <use xlink:href="#issue-parent"></use>
- </svg>
-
- </div>
- <h2>issue-parent</h2>
- </li>
- <li title="issues">
- <div class="icon-box">
-
- <!-- issues -->
- <svg class="svg-issues-dims">
- <use xlink:href="#issues"></use>
- </svg>
-
- </div>
- <h2>issues</h2>
- </li>
- <li title="key">
- <div class="icon-box">
-
- <!-- key -->
- <svg class="svg-key-dims">
- <use xlink:href="#key"></use>
- </svg>
-
- </div>
- <h2>key</h2>
- </li>
- <li title="key-2">
- <div class="icon-box">
-
- <!-- key-2 -->
- <svg class="svg-key-2-dims">
- <use xlink:href="#key-2"></use>
- </svg>
-
- </div>
- <h2>key-2</h2>
- </li>
- <li title="label">
- <div class="icon-box">
-
- <!-- label -->
- <svg class="svg-label-dims">
- <use xlink:href="#label"></use>
- </svg>
-
- </div>
- <h2>label</h2>
- </li>
- <li title="labels">
- <div class="icon-box">
-
- <!-- labels -->
- <svg class="svg-labels-dims">
- <use xlink:href="#labels"></use>
- </svg>
-
- </div>
- <h2>labels</h2>
- </li>
- <li title="leave">
- <div class="icon-box">
-
- <!-- leave -->
- <svg class="svg-leave-dims">
- <use xlink:href="#leave"></use>
- </svg>
-
- </div>
- <h2>leave</h2>
- </li>
- <li title="level-up">
- <div class="icon-box">
-
- <!-- level-up -->
- <svg class="svg-level-up-dims">
- <use xlink:href="#level-up"></use>
- </svg>
-
- </div>
- <h2>level-up</h2>
- </li>
- <li title="license">
- <div class="icon-box">
-
- <!-- license -->
- <svg class="svg-license-dims">
- <use xlink:href="#license"></use>
- </svg>
-
- </div>
- <h2>license</h2>
- </li>
- <li title="link">
- <div class="icon-box">
-
- <!-- link -->
- <svg class="svg-link-dims">
- <use xlink:href="#link"></use>
- </svg>
-
- </div>
- <h2>link</h2>
- </li>
- <li title="list-bulleted">
- <div class="icon-box">
-
- <!-- list-bulleted -->
- <svg class="svg-list-bulleted-dims">
- <use xlink:href="#list-bulleted"></use>
- </svg>
-
- </div>
- <h2>list-bulleted</h2>
- </li>
- <li title="list-numbered">
- <div class="icon-box">
-
- <!-- list-numbered -->
- <svg class="svg-list-numbered-dims">
- <use xlink:href="#list-numbered"></use>
- </svg>
-
- </div>
- <h2>list-numbered</h2>
- </li>
- <li title="location">
- <div class="icon-box">
-
- <!-- location -->
- <svg class="svg-location-dims">
- <use xlink:href="#location"></use>
- </svg>
-
- </div>
- <h2>location</h2>
- </li>
- <li title="location-dot">
- <div class="icon-box">
-
- <!-- location-dot -->
- <svg class="svg-location-dot-dims">
- <use xlink:href="#location-dot"></use>
- </svg>
-
- </div>
- <h2>location-dot</h2>
- </li>
- <li title="lock">
- <div class="icon-box">
-
- <!-- lock -->
- <svg class="svg-lock-dims">
- <use xlink:href="#lock"></use>
- </svg>
-
- </div>
- <h2>lock</h2>
- </li>
- <li title="lock-open">
- <div class="icon-box">
-
- <!-- lock-open -->
- <svg class="svg-lock-open-dims">
- <use xlink:href="#lock-open"></use>
- </svg>
-
- </div>
- <h2>lock-open</h2>
- </li>
- <li title="log">
- <div class="icon-box">
-
- <!-- log -->
- <svg class="svg-log-dims">
- <use xlink:href="#log"></use>
- </svg>
-
- </div>
- <h2>log</h2>
- </li>
- <li title="mail">
- <div class="icon-box">
-
- <!-- mail -->
- <svg class="svg-mail-dims">
- <use xlink:href="#mail"></use>
- </svg>
-
- </div>
- <h2>mail</h2>
- </li>
- <li title="merge-request-close">
- <div class="icon-box">
-
- <!-- merge-request-close -->
- <svg class="svg-merge-request-close-dims">
- <use xlink:href="#merge-request-close"></use>
- </svg>
-
- </div>
- <h2>merge-request-close</h2>
- </li>
- <li title="merge-request-close-m">
- <div class="icon-box">
-
- <!-- merge-request-close-m -->
- <svg class="svg-merge-request-close-m-dims">
- <use xlink:href="#merge-request-close-m"></use>
- </svg>
-
- </div>
- <h2>merge-request-close-m</h2>
- </li>
- <li title="messages">
- <div class="icon-box">
-
- <!-- messages -->
- <svg class="svg-messages-dims">
- <use xlink:href="#messages"></use>
- </svg>
-
- </div>
- <h2>messages</h2>
- </li>
- <li title="mobile-issue-close">
- <div class="icon-box">
-
- <!-- mobile-issue-close -->
- <svg class="svg-mobile-issue-close-dims">
- <use xlink:href="#mobile-issue-close"></use>
- </svg>
-
- </div>
- <h2>mobile-issue-close</h2>
- </li>
- <li title="monitor">
- <div class="icon-box">
-
- <!-- monitor -->
- <svg class="svg-monitor-dims">
- <use xlink:href="#monitor"></use>
- </svg>
-
- </div>
- <h2>monitor</h2>
- </li>
- <li title="more">
- <div class="icon-box">
-
- <!-- more -->
- <svg class="svg-more-dims">
- <use xlink:href="#more"></use>
- </svg>
-
- </div>
- <h2>more</h2>
- </li>
- <li title="notifications">
- <div class="icon-box">
-
- <!-- notifications -->
- <svg class="svg-notifications-dims">
- <use xlink:href="#notifications"></use>
- </svg>
-
- </div>
- <h2>notifications</h2>
- </li>
- <li title="notifications-off">
- <div class="icon-box">
-
- <!-- notifications-off -->
- <svg class="svg-notifications-off-dims">
- <use xlink:href="#notifications-off"></use>
- </svg>
-
- </div>
- <h2>notifications-off</h2>
- </li>
- <li title="overview">
- <div class="icon-box">
-
- <!-- overview -->
- <svg class="svg-overview-dims">
- <use xlink:href="#overview"></use>
- </svg>
-
- </div>
- <h2>overview</h2>
- </li>
- <li title="pencil">
- <div class="icon-box">
-
- <!-- pencil -->
- <svg class="svg-pencil-dims">
- <use xlink:href="#pencil"></use>
- </svg>
-
- </div>
- <h2>pencil</h2>
- </li>
- <li title="pipeline">
- <div class="icon-box">
-
- <!-- pipeline -->
- <svg class="svg-pipeline-dims">
- <use xlink:href="#pipeline"></use>
- </svg>
-
- </div>
- <h2>pipeline</h2>
- </li>
- <li title="play">
- <div class="icon-box">
-
- <!-- play -->
- <svg class="svg-play-dims">
- <use xlink:href="#play"></use>
- </svg>
-
- </div>
- <h2>play</h2>
- </li>
- <li title="plus">
- <div class="icon-box">
-
- <!-- plus -->
- <svg class="svg-plus-dims">
- <use xlink:href="#plus"></use>
- </svg>
-
- </div>
- <h2>plus</h2>
- </li>
- <li title="plus-square">
- <div class="icon-box">
-
- <!-- plus-square -->
- <svg class="svg-plus-square-dims">
- <use xlink:href="#plus-square"></use>
- </svg>
-
- </div>
- <h2>plus-square</h2>
- </li>
- <li title="plus-square-o">
- <div class="icon-box">
-
- <!-- plus-square-o -->
- <svg class="svg-plus-square-o-dims">
- <use xlink:href="#plus-square-o"></use>
- </svg>
-
- </div>
- <h2>plus-square-o</h2>
- </li>
- <li title="preferences">
- <div class="icon-box">
-
- <!-- preferences -->
- <svg class="svg-preferences-dims">
- <use xlink:href="#preferences"></use>
- </svg>
-
- </div>
- <h2>preferences</h2>
- </li>
- <li title="profile">
- <div class="icon-box">
-
- <!-- profile -->
- <svg class="svg-profile-dims">
- <use xlink:href="#profile"></use>
- </svg>
-
- </div>
- <h2>profile</h2>
- </li>
- <li title="project">
- <div class="icon-box">
-
- <!-- project -->
- <svg class="svg-project-dims">
- <use xlink:href="#project"></use>
- </svg>
-
- </div>
- <h2>project</h2>
- </li>
- <li title="push-rules">
- <div class="icon-box">
-
- <!-- push-rules -->
- <svg class="svg-push-rules-dims">
- <use xlink:href="#push-rules"></use>
- </svg>
-
- </div>
- <h2>push-rules</h2>
- </li>
- <li title="question">
- <div class="icon-box">
-
- <!-- question -->
- <svg class="svg-question-dims">
- <use xlink:href="#question"></use>
- </svg>
-
- </div>
- <h2>question</h2>
- </li>
- <li title="question-o">
- <div class="icon-box">
-
- <!-- question-o -->
- <svg class="svg-question-o-dims">
- <use xlink:href="#question-o"></use>
- </svg>
-
- </div>
- <h2>question-o</h2>
- </li>
- <li title="quote">
- <div class="icon-box">
-
- <!-- quote -->
- <svg class="svg-quote-dims">
- <use xlink:href="#quote"></use>
- </svg>
-
- </div>
- <h2>quote</h2>
- </li>
- <li title="redo">
- <div class="icon-box">
-
- <!-- redo -->
- <svg class="svg-redo-dims">
- <use xlink:href="#redo"></use>
- </svg>
-
- </div>
- <h2>redo</h2>
- </li>
- <li title="remove">
- <div class="icon-box">
-
- <!-- remove -->
- <svg class="svg-remove-dims">
- <use xlink:href="#remove"></use>
- </svg>
-
- </div>
- <h2>remove</h2>
- </li>
- <li title="repeat">
- <div class="icon-box">
-
- <!-- repeat -->
- <svg class="svg-repeat-dims">
- <use xlink:href="#repeat"></use>
- </svg>
-
- </div>
- <h2>repeat</h2>
- </li>
- <li title="retry">
- <div class="icon-box">
-
- <!-- retry -->
- <svg class="svg-retry-dims">
- <use xlink:href="#retry"></use>
- </svg>
-
- </div>
- <h2>retry</h2>
- </li>
- <li title="scale">
- <div class="icon-box">
-
- <!-- scale -->
- <svg class="svg-scale-dims">
- <use xlink:href="#scale"></use>
- </svg>
-
- </div>
- <h2>scale</h2>
- </li>
- <li title="screen-full">
- <div class="icon-box">
-
- <!-- screen-full -->
- <svg class="svg-screen-full-dims">
- <use xlink:href="#screen-full"></use>
- </svg>
-
- </div>
- <h2>screen-full</h2>
- </li>
- <li title="screen-normal">
- <div class="icon-box">
-
- <!-- screen-normal -->
- <svg class="svg-screen-normal-dims">
- <use xlink:href="#screen-normal"></use>
- </svg>
-
- </div>
- <h2>screen-normal</h2>
- </li>
- <li title="search">
- <div class="icon-box">
-
- <!-- search -->
- <svg class="svg-search-dims">
- <use xlink:href="#search"></use>
- </svg>
-
- </div>
- <h2>search</h2>
- </li>
- <li title="settings">
- <div class="icon-box">
-
- <!-- settings -->
- <svg class="svg-settings-dims">
- <use xlink:href="#settings"></use>
- </svg>
-
- </div>
- <h2>settings</h2>
- </li>
- <li title="shield">
- <div class="icon-box">
-
- <!-- shield -->
- <svg class="svg-shield-dims">
- <use xlink:href="#shield"></use>
- </svg>
-
- </div>
- <h2>shield</h2>
- </li>
- <li title="slight-frown">
- <div class="icon-box">
-
- <!-- slight-frown -->
- <svg class="svg-slight-frown-dims">
- <use xlink:href="#slight-frown"></use>
- </svg>
-
- </div>
- <h2>slight-frown</h2>
- </li>
- <li title="slight-smile">
- <div class="icon-box">
-
- <!-- slight-smile -->
- <svg class="svg-slight-smile-dims">
- <use xlink:href="#slight-smile"></use>
- </svg>
-
- </div>
- <h2>slight-smile</h2>
- </li>
- <li title="smile">
- <div class="icon-box">
-
- <!-- smile -->
- <svg class="svg-smile-dims">
- <use xlink:href="#smile"></use>
- </svg>
-
- </div>
- <h2>smile</h2>
- </li>
- <li title="smiley">
- <div class="icon-box">
-
- <!-- smiley -->
- <svg class="svg-smiley-dims">
- <use xlink:href="#smiley"></use>
- </svg>
-
- </div>
- <h2>smiley</h2>
- </li>
- <li title="snippet">
- <div class="icon-box">
-
- <!-- snippet -->
- <svg class="svg-snippet-dims">
- <use xlink:href="#snippet"></use>
- </svg>
-
- </div>
- <h2>snippet</h2>
- </li>
- <li title="spam">
- <div class="icon-box">
-
- <!-- spam -->
- <svg class="svg-spam-dims">
- <use xlink:href="#spam"></use>
- </svg>
-
- </div>
- <h2>spam</h2>
- </li>
- <li title="star">
- <div class="icon-box">
-
- <!-- star -->
- <svg class="svg-star-dims">
- <use xlink:href="#star"></use>
- </svg>
-
- </div>
- <h2>star</h2>
- </li>
- <li title="star-o">
- <div class="icon-box">
-
- <!-- star-o -->
- <svg class="svg-star-o-dims">
- <use xlink:href="#star-o"></use>
- </svg>
-
- </div>
- <h2>star-o</h2>
- </li>
- <li title="stop">
- <div class="icon-box">
-
- <!-- stop -->
- <svg class="svg-stop-dims">
- <use xlink:href="#stop"></use>
- </svg>
-
- </div>
- <h2>stop</h2>
- </li>
- <li title="talic">
- <div class="icon-box">
-
- <!-- talic -->
- <svg class="svg-talic-dims">
- <use xlink:href="#talic"></use>
- </svg>
-
- </div>
- <h2>talic</h2>
- </li>
- <li title="task-done">
- <div class="icon-box">
-
- <!-- task-done -->
- <svg class="svg-task-done-dims">
- <use xlink:href="#task-done"></use>
- </svg>
-
- </div>
- <h2>task-done</h2>
- </li>
- <li title="template">
- <div class="icon-box">
-
- <!-- template -->
- <svg class="svg-template-dims">
- <use xlink:href="#template"></use>
- </svg>
-
- </div>
- <h2>template</h2>
- </li>
- <li title="thump-down">
- <div class="icon-box">
-
- <!-- thump-down -->
- <svg class="svg-thump-down-dims">
- <use xlink:href="#thump-down"></use>
- </svg>
-
- </div>
- <h2>thump-down</h2>
- </li>
- <li title="thump-up">
- <div class="icon-box">
-
- <!-- thump-up -->
- <svg class="svg-thump-up-dims">
- <use xlink:href="#thump-up"></use>
- </svg>
-
- </div>
- <h2>thump-up</h2>
- </li>
- <li title="timer">
- <div class="icon-box">
-
- <!-- timer -->
- <svg class="svg-timer-dims">
- <use xlink:href="#timer"></use>
- </svg>
-
- </div>
- <h2>timer</h2>
- </li>
- <li title="todo-add">
- <div class="icon-box">
-
- <!-- todo-add -->
- <svg class="svg-todo-add-dims">
- <use xlink:href="#todo-add"></use>
- </svg>
-
- </div>
- <h2>todo-add</h2>
- </li>
- <li title="todo-done">
- <div class="icon-box">
-
- <!-- todo-done -->
- <svg class="svg-todo-done-dims">
- <use xlink:href="#todo-done"></use>
- </svg>
-
- </div>
- <h2>todo-done</h2>
- </li>
- <li title="token">
- <div class="icon-box">
-
- <!-- token -->
- <svg class="svg-token-dims">
- <use xlink:href="#token"></use>
- </svg>
-
- </div>
- <h2>token</h2>
- </li>
- <li title="unapproval">
- <div class="icon-box">
-
- <!-- unapproval -->
- <svg class="svg-unapproval-dims">
- <use xlink:href="#unapproval"></use>
- </svg>
-
- </div>
- <h2>unapproval</h2>
- </li>
- <li title="unassignee">
- <div class="icon-box">
-
- <!-- unassignee -->
- <svg class="svg-unassignee-dims">
- <use xlink:href="#unassignee"></use>
- </svg>
-
- </div>
- <h2>unassignee</h2>
- </li>
- <li title="unlink">
- <div class="icon-box">
-
- <!-- unlink -->
- <svg class="svg-unlink-dims">
- <use xlink:href="#unlink"></use>
- </svg>
-
- </div>
- <h2>unlink</h2>
- </li>
- <li title="user">
- <div class="icon-box">
-
- <!-- user -->
- <svg class="svg-user-dims">
- <use xlink:href="#user"></use>
- </svg>
-
- </div>
- <h2>user</h2>
- </li>
- <li title="users">
- <div class="icon-box">
-
- <!-- users -->
- <svg class="svg-users-dims">
- <use xlink:href="#users"></use>
- </svg>
-
- </div>
- <h2>users</h2>
- </li>
- <li title="volume-up">
- <div class="icon-box">
-
- <!-- volume-up -->
- <svg class="svg-volume-up-dims">
- <use xlink:href="#volume-up"></use>
- </svg>
-
- </div>
- <h2>volume-up</h2>
- </li>
- <li title="warning">
- <div class="icon-box">
-
- <!-- warning -->
- <svg class="svg-warning-dims">
- <use xlink:href="#warning"></use>
- </svg>
-
- </div>
- <h2>warning</h2>
- </li>
- <li title="work">
- <div class="icon-box">
-
- <!-- work -->
- <svg class="svg-work-dims">
- <use xlink:href="#work"></use>
- </svg>
-
- </div>
- <h2>work</h2>
- </li>
- </ul>
-
-<!--
-====================================================================================================
--->
-
- </section>
- <section>
-
-<!--
-
-B) Inline SVG with external sprite (IE 9-11 with polyfill only)
-====================================================================================================
-These SVG images make use of an URL + fragment identifiers (IDs) and refer to the regular external
-SVG sprite. They may be styled via CSS. (IE 9-11 with polyfill only)
-
--->
-
- <h3>B) Inline SVG with external sprite (IE 9-11 with polyfill only)</h3>
- <ul>
-
- <li title="abuse">
- <div class="icon-box">
-
- <!-- abuse -->
- <svg class="svg-abuse-dims">
- <use xlink:href="icons.svg#abuse"></use>
- </svg>
-
- </div>
- <h2>abuse</h2>
- </li>
- <li title="account">
- <div class="icon-box">
-
- <!-- account -->
- <svg class="svg-account-dims">
- <use xlink:href="icons.svg#account"></use>
- </svg>
-
- </div>
- <h2>account</h2>
- </li>
- <li title="admin">
- <div class="icon-box">
-
- <!-- admin -->
- <svg class="svg-admin-dims">
- <use xlink:href="icons.svg#admin"></use>
- </svg>
-
- </div>
- <h2>admin</h2>
- </li>
- <li title="angle-double-left">
- <div class="icon-box">
-
- <!-- angle-double-left -->
- <svg class="svg-angle-double-left-dims">
- <use xlink:href="icons.svg#angle-double-left"></use>
- </svg>
-
- </div>
- <h2>angle-double-left</h2>
- </li>
- <li title="angle-down">
- <div class="icon-box">
-
- <!-- angle-down -->
- <svg class="svg-angle-down-dims">
- <use xlink:href="icons.svg#angle-down"></use>
- </svg>
-
- </div>
- <h2>angle-down</h2>
- </li>
- <li title="angle-left">
- <div class="icon-box">
-
- <!-- angle-left -->
- <svg class="svg-angle-left-dims">
- <use xlink:href="icons.svg#angle-left"></use>
- </svg>
-
- </div>
- <h2>angle-left</h2>
- </li>
- <li title="angle-right">
- <div class="icon-box">
-
- <!-- angle-right -->
- <svg class="svg-angle-right-dims">
- <use xlink:href="icons.svg#angle-right"></use>
- </svg>
-
- </div>
- <h2>angle-right</h2>
- </li>
- <li title="angle-up">
- <div class="icon-box">
-
- <!-- angle-up -->
- <svg class="svg-angle-up-dims">
- <use xlink:href="icons.svg#angle-up"></use>
- </svg>
-
- </div>
- <h2>angle-up</h2>
- </li>
- <li title="appearance">
- <div class="icon-box">
-
- <!-- appearance -->
- <svg class="svg-appearance-dims">
- <use xlink:href="icons.svg#appearance"></use>
- </svg>
-
- </div>
- <h2>appearance</h2>
- </li>
- <li title="applications">
- <div class="icon-box">
-
- <!-- applications -->
- <svg class="svg-applications-dims">
- <use xlink:href="icons.svg#applications"></use>
- </svg>
-
- </div>
- <h2>applications</h2>
- </li>
- <li title="approval">
- <div class="icon-box">
-
- <!-- approval -->
- <svg class="svg-approval-dims">
- <use xlink:href="icons.svg#approval"></use>
- </svg>
-
- </div>
- <h2>approval</h2>
- </li>
- <li title="arrow-right">
- <div class="icon-box">
-
- <!-- arrow-right -->
- <svg class="svg-arrow-right-dims">
- <use xlink:href="icons.svg#arrow-right"></use>
- </svg>
-
- </div>
- <h2>arrow-right</h2>
- </li>
- <li title="assignee">
- <div class="icon-box">
-
- <!-- assignee -->
- <svg class="svg-assignee-dims">
- <use xlink:href="icons.svg#assignee"></use>
- </svg>
-
- </div>
- <h2>assignee</h2>
- </li>
- <li title="bold">
- <div class="icon-box">
-
- <!-- bold -->
- <svg class="svg-bold-dims">
- <use xlink:href="icons.svg#bold"></use>
- </svg>
-
- </div>
- <h2>bold</h2>
- </li>
- <li title="book">
- <div class="icon-box">
-
- <!-- book -->
- <svg class="svg-book-dims">
- <use xlink:href="icons.svg#book"></use>
- </svg>
-
- </div>
- <h2>book</h2>
- </li>
- <li title="branch">
- <div class="icon-box">
-
- <!-- branch -->
- <svg class="svg-branch-dims">
- <use xlink:href="icons.svg#branch"></use>
- </svg>
-
- </div>
- <h2>branch</h2>
- </li>
- <li title="calendar">
- <div class="icon-box">
-
- <!-- calendar -->
- <svg class="svg-calendar-dims">
- <use xlink:href="icons.svg#calendar"></use>
- </svg>
-
- </div>
- <h2>calendar</h2>
- </li>
- <li title="cancel">
- <div class="icon-box">
-
- <!-- cancel -->
- <svg class="svg-cancel-dims">
- <use xlink:href="icons.svg#cancel"></use>
- </svg>
-
- </div>
- <h2>cancel</h2>
- </li>
- <li title="chevron-down">
- <div class="icon-box">
-
- <!-- chevron-down -->
- <svg class="svg-chevron-down-dims">
- <use xlink:href="icons.svg#chevron-down"></use>
- </svg>
-
- </div>
- <h2>chevron-down</h2>
- </li>
- <li title="chevron-left">
- <div class="icon-box">
-
- <!-- chevron-left -->
- <svg class="svg-chevron-left-dims">
- <use xlink:href="icons.svg#chevron-left"></use>
- </svg>
-
- </div>
- <h2>chevron-left</h2>
- </li>
- <li title="chevron-right">
- <div class="icon-box">
-
- <!-- chevron-right -->
- <svg class="svg-chevron-right-dims">
- <use xlink:href="icons.svg#chevron-right"></use>
- </svg>
-
- </div>
- <h2>chevron-right</h2>
- </li>
- <li title="chevron-up">
- <div class="icon-box">
-
- <!-- chevron-up -->
- <svg class="svg-chevron-up-dims">
- <use xlink:href="icons.svg#chevron-up"></use>
- </svg>
-
- </div>
- <h2>chevron-up</h2>
- </li>
- <li title="clock">
- <div class="icon-box">
-
- <!-- clock -->
- <svg class="svg-clock-dims">
- <use xlink:href="icons.svg#clock"></use>
- </svg>
-
- </div>
- <h2>clock</h2>
- </li>
- <li title="code">
- <div class="icon-box">
-
- <!-- code -->
- <svg class="svg-code-dims">
- <use xlink:href="icons.svg#code"></use>
- </svg>
-
- </div>
- <h2>code</h2>
- </li>
- <li title="comment">
- <div class="icon-box">
-
- <!-- comment -->
- <svg class="svg-comment-dims">
- <use xlink:href="icons.svg#comment"></use>
- </svg>
-
- </div>
- <h2>comment</h2>
- </li>
- <li title="comment-dots">
- <div class="icon-box">
-
- <!-- comment-dots -->
- <svg class="svg-comment-dots-dims">
- <use xlink:href="icons.svg#comment-dots"></use>
- </svg>
-
- </div>
- <h2>comment-dots</h2>
- </li>
- <li title="comment-next">
- <div class="icon-box">
-
- <!-- comment-next -->
- <svg class="svg-comment-next-dims">
- <use xlink:href="icons.svg#comment-next"></use>
- </svg>
-
- </div>
- <h2>comment-next</h2>
- </li>
- <li title="comments">
- <div class="icon-box">
-
- <!-- comments -->
- <svg class="svg-comments-dims">
- <use xlink:href="icons.svg#comments"></use>
- </svg>
-
- </div>
- <h2>comments</h2>
- </li>
- <li title="commit">
- <div class="icon-box">
-
- <!-- commit -->
- <svg class="svg-commit-dims">
- <use xlink:href="icons.svg#commit"></use>
- </svg>
-
- </div>
- <h2>commit</h2>
- </li>
- <li title="credit-card">
- <div class="icon-box">
-
- <!-- credit-card -->
- <svg class="svg-credit-card-dims">
- <use xlink:href="icons.svg#credit-card"></use>
- </svg>
-
- </div>
- <h2>credit-card</h2>
- </li>
- <li title="disk">
- <div class="icon-box">
-
- <!-- disk -->
- <svg class="svg-disk-dims">
- <use xlink:href="icons.svg#disk"></use>
- </svg>
-
- </div>
- <h2>disk</h2>
- </li>
- <li title="doc_code">
- <div class="icon-box">
-
- <!-- doc_code -->
- <svg class="svg-doc_code-dims">
- <use xlink:href="icons.svg#doc_code"></use>
- </svg>
-
- </div>
- <h2>doc_code</h2>
- </li>
- <li title="doc_image">
- <div class="icon-box">
-
- <!-- doc_image -->
- <svg class="svg-doc_image-dims">
- <use xlink:href="icons.svg#doc_image"></use>
- </svg>
-
- </div>
- <h2>doc_image</h2>
- </li>
- <li title="doc_text">
- <div class="icon-box">
-
- <!-- doc_text -->
- <svg class="svg-doc_text-dims">
- <use xlink:href="icons.svg#doc_text"></use>
- </svg>
-
- </div>
- <h2>doc_text</h2>
- </li>
- <li title="download">
- <div class="icon-box">
-
- <!-- download -->
- <svg class="svg-download-dims">
- <use xlink:href="icons.svg#download"></use>
- </svg>
-
- </div>
- <h2>download</h2>
- </li>
- <li title="duplicate">
- <div class="icon-box">
-
- <!-- duplicate -->
- <svg class="svg-duplicate-dims">
- <use xlink:href="icons.svg#duplicate"></use>
- </svg>
-
- </div>
- <h2>duplicate</h2>
- </li>
- <li title="earth">
- <div class="icon-box">
-
- <!-- earth -->
- <svg class="svg-earth-dims">
- <use xlink:href="icons.svg#earth"></use>
- </svg>
-
- </div>
- <h2>earth</h2>
- </li>
- <li title="eye">
- <div class="icon-box">
-
- <!-- eye -->
- <svg class="svg-eye-dims">
- <use xlink:href="icons.svg#eye"></use>
- </svg>
-
- </div>
- <h2>eye</h2>
- </li>
- <li title="eye-slash">
- <div class="icon-box">
-
- <!-- eye-slash -->
- <svg class="svg-eye-slash-dims">
- <use xlink:href="icons.svg#eye-slash"></use>
- </svg>
-
- </div>
- <h2>eye-slash</h2>
- </li>
- <li title="file-additions">
- <div class="icon-box">
-
- <!-- file-additions -->
- <svg class="svg-file-additions-dims">
- <use xlink:href="icons.svg#file-additions"></use>
- </svg>
-
- </div>
- <h2>file-additions</h2>
- </li>
- <li title="file-deletion">
- <div class="icon-box">
-
- <!-- file-deletion -->
- <svg class="svg-file-deletion-dims">
- <use xlink:href="icons.svg#file-deletion"></use>
- </svg>
-
- </div>
- <h2>file-deletion</h2>
- </li>
- <li title="file-modified">
- <div class="icon-box">
-
- <!-- file-modified -->
- <svg class="svg-file-modified-dims">
- <use xlink:href="icons.svg#file-modified"></use>
- </svg>
-
- </div>
- <h2>file-modified</h2>
- </li>
- <li title="filter">
- <div class="icon-box">
-
- <!-- filter -->
- <svg class="svg-filter-dims">
- <use xlink:href="icons.svg#filter"></use>
- </svg>
-
- </div>
- <h2>filter</h2>
- </li>
- <li title="folder">
- <div class="icon-box">
-
- <!-- folder -->
- <svg class="svg-folder-dims">
- <use xlink:href="icons.svg#folder"></use>
- </svg>
-
- </div>
- <h2>folder</h2>
- </li>
- <li title="fork">
- <div class="icon-box">
-
- <!-- fork -->
- <svg class="svg-fork-dims">
- <use xlink:href="icons.svg#fork"></use>
- </svg>
-
- </div>
- <h2>fork</h2>
- </li>
- <li title="git-merge">
- <div class="icon-box">
-
- <!-- git-merge -->
- <svg class="svg-git-merge-dims">
- <use xlink:href="icons.svg#git-merge"></use>
- </svg>
-
- </div>
- <h2>git-merge</h2>
- </li>
- <li title="group">
- <div class="icon-box">
-
- <!-- group -->
- <svg class="svg-group-dims">
- <use xlink:href="icons.svg#group"></use>
- </svg>
-
- </div>
- <h2>group</h2>
- </li>
- <li title="history">
- <div class="icon-box">
-
- <!-- history -->
- <svg class="svg-history-dims">
- <use xlink:href="icons.svg#history"></use>
- </svg>
-
- </div>
- <h2>history</h2>
- </li>
- <li title="home">
- <div class="icon-box">
-
- <!-- home -->
- <svg class="svg-home-dims">
- <use xlink:href="icons.svg#home"></use>
- </svg>
-
- </div>
- <h2>home</h2>
- </li>
- <li title="hook">
- <div class="icon-box">
-
- <!-- hook -->
- <svg class="svg-hook-dims">
- <use xlink:href="icons.svg#hook"></use>
- </svg>
-
- </div>
- <h2>hook</h2>
- </li>
- <li title="issue-block">
- <div class="icon-box">
-
- <!-- issue-block -->
- <svg class="svg-issue-block-dims">
- <use xlink:href="icons.svg#issue-block"></use>
- </svg>
-
- </div>
- <h2>issue-block</h2>
- </li>
- <li title="issue-child">
- <div class="icon-box">
-
- <!-- issue-child -->
- <svg class="svg-issue-child-dims">
- <use xlink:href="icons.svg#issue-child"></use>
- </svg>
-
- </div>
- <h2>issue-child</h2>
- </li>
- <li title="issue-close">
- <div class="icon-box">
-
- <!-- issue-close -->
- <svg class="svg-issue-close-dims">
- <use xlink:href="icons.svg#issue-close"></use>
- </svg>
-
- </div>
- <h2>issue-close</h2>
- </li>
- <li title="issue-duplicate">
- <div class="icon-box">
-
- <!-- issue-duplicate -->
- <svg class="svg-issue-duplicate-dims">
- <use xlink:href="icons.svg#issue-duplicate"></use>
- </svg>
-
- </div>
- <h2>issue-duplicate</h2>
- </li>
- <li title="issue-new">
- <div class="icon-box">
-
- <!-- issue-new -->
- <svg class="svg-issue-new-dims">
- <use xlink:href="icons.svg#issue-new"></use>
- </svg>
-
- </div>
- <h2>issue-new</h2>
- </li>
- <li title="issue-open">
- <div class="icon-box">
-
- <!-- issue-open -->
- <svg class="svg-issue-open-dims">
- <use xlink:href="icons.svg#issue-open"></use>
- </svg>
-
- </div>
- <h2>issue-open</h2>
- </li>
- <li title="issue-open-m">
- <div class="icon-box">
-
- <!-- issue-open-m -->
- <svg class="svg-issue-open-m-dims">
- <use xlink:href="icons.svg#issue-open-m"></use>
- </svg>
-
- </div>
- <h2>issue-open-m</h2>
- </li>
- <li title="issue-parent">
- <div class="icon-box">
-
- <!-- issue-parent -->
- <svg class="svg-issue-parent-dims">
- <use xlink:href="icons.svg#issue-parent"></use>
- </svg>
-
- </div>
- <h2>issue-parent</h2>
- </li>
- <li title="issues">
- <div class="icon-box">
-
- <!-- issues -->
- <svg class="svg-issues-dims">
- <use xlink:href="icons.svg#issues"></use>
- </svg>
-
- </div>
- <h2>issues</h2>
- </li>
- <li title="key">
- <div class="icon-box">
-
- <!-- key -->
- <svg class="svg-key-dims">
- <use xlink:href="icons.svg#key"></use>
- </svg>
-
- </div>
- <h2>key</h2>
- </li>
- <li title="key-2">
- <div class="icon-box">
-
- <!-- key-2 -->
- <svg class="svg-key-2-dims">
- <use xlink:href="icons.svg#key-2"></use>
- </svg>
-
- </div>
- <h2>key-2</h2>
- </li>
- <li title="label">
- <div class="icon-box">
-
- <!-- label -->
- <svg class="svg-label-dims">
- <use xlink:href="icons.svg#label"></use>
- </svg>
-
- </div>
- <h2>label</h2>
- </li>
- <li title="labels">
- <div class="icon-box">
-
- <!-- labels -->
- <svg class="svg-labels-dims">
- <use xlink:href="icons.svg#labels"></use>
- </svg>
-
- </div>
- <h2>labels</h2>
- </li>
- <li title="leave">
- <div class="icon-box">
-
- <!-- leave -->
- <svg class="svg-leave-dims">
- <use xlink:href="icons.svg#leave"></use>
- </svg>
-
- </div>
- <h2>leave</h2>
- </li>
- <li title="level-up">
- <div class="icon-box">
-
- <!-- level-up -->
- <svg class="svg-level-up-dims">
- <use xlink:href="icons.svg#level-up"></use>
- </svg>
-
- </div>
- <h2>level-up</h2>
- </li>
- <li title="license">
- <div class="icon-box">
-
- <!-- license -->
- <svg class="svg-license-dims">
- <use xlink:href="icons.svg#license"></use>
- </svg>
-
- </div>
- <h2>license</h2>
- </li>
- <li title="link">
- <div class="icon-box">
-
- <!-- link -->
- <svg class="svg-link-dims">
- <use xlink:href="icons.svg#link"></use>
- </svg>
-
- </div>
- <h2>link</h2>
- </li>
- <li title="list-bulleted">
- <div class="icon-box">
-
- <!-- list-bulleted -->
- <svg class="svg-list-bulleted-dims">
- <use xlink:href="icons.svg#list-bulleted"></use>
- </svg>
-
- </div>
- <h2>list-bulleted</h2>
- </li>
- <li title="list-numbered">
- <div class="icon-box">
-
- <!-- list-numbered -->
- <svg class="svg-list-numbered-dims">
- <use xlink:href="icons.svg#list-numbered"></use>
- </svg>
-
- </div>
- <h2>list-numbered</h2>
- </li>
- <li title="location">
- <div class="icon-box">
-
- <!-- location -->
- <svg class="svg-location-dims">
- <use xlink:href="icons.svg#location"></use>
- </svg>
-
- </div>
- <h2>location</h2>
- </li>
- <li title="location-dot">
- <div class="icon-box">
-
- <!-- location-dot -->
- <svg class="svg-location-dot-dims">
- <use xlink:href="icons.svg#location-dot"></use>
- </svg>
-
- </div>
- <h2>location-dot</h2>
- </li>
- <li title="lock">
- <div class="icon-box">
-
- <!-- lock -->
- <svg class="svg-lock-dims">
- <use xlink:href="icons.svg#lock"></use>
- </svg>
-
- </div>
- <h2>lock</h2>
- </li>
- <li title="lock-open">
- <div class="icon-box">
-
- <!-- lock-open -->
- <svg class="svg-lock-open-dims">
- <use xlink:href="icons.svg#lock-open"></use>
- </svg>
-
- </div>
- <h2>lock-open</h2>
- </li>
- <li title="log">
- <div class="icon-box">
-
- <!-- log -->
- <svg class="svg-log-dims">
- <use xlink:href="icons.svg#log"></use>
- </svg>
-
- </div>
- <h2>log</h2>
- </li>
- <li title="mail">
- <div class="icon-box">
-
- <!-- mail -->
- <svg class="svg-mail-dims">
- <use xlink:href="icons.svg#mail"></use>
- </svg>
-
- </div>
- <h2>mail</h2>
- </li>
- <li title="merge-request-close">
- <div class="icon-box">
-
- <!-- merge-request-close -->
- <svg class="svg-merge-request-close-dims">
- <use xlink:href="icons.svg#merge-request-close"></use>
- </svg>
-
- </div>
- <h2>merge-request-close</h2>
- </li>
- <li title="merge-request-close-m">
- <div class="icon-box">
-
- <!-- merge-request-close-m -->
- <svg class="svg-merge-request-close-m-dims">
- <use xlink:href="icons.svg#merge-request-close-m"></use>
- </svg>
-
- </div>
- <h2>merge-request-close-m</h2>
- </li>
- <li title="messages">
- <div class="icon-box">
-
- <!-- messages -->
- <svg class="svg-messages-dims">
- <use xlink:href="icons.svg#messages"></use>
- </svg>
-
- </div>
- <h2>messages</h2>
- </li>
- <li title="mobile-issue-close">
- <div class="icon-box">
-
- <!-- mobile-issue-close -->
- <svg class="svg-mobile-issue-close-dims">
- <use xlink:href="icons.svg#mobile-issue-close"></use>
- </svg>
-
- </div>
- <h2>mobile-issue-close</h2>
- </li>
- <li title="monitor">
- <div class="icon-box">
-
- <!-- monitor -->
- <svg class="svg-monitor-dims">
- <use xlink:href="icons.svg#monitor"></use>
- </svg>
-
- </div>
- <h2>monitor</h2>
- </li>
- <li title="more">
- <div class="icon-box">
-
- <!-- more -->
- <svg class="svg-more-dims">
- <use xlink:href="icons.svg#more"></use>
- </svg>
-
- </div>
- <h2>more</h2>
- </li>
- <li title="notifications">
- <div class="icon-box">
-
- <!-- notifications -->
- <svg class="svg-notifications-dims">
- <use xlink:href="icons.svg#notifications"></use>
- </svg>
-
- </div>
- <h2>notifications</h2>
- </li>
- <li title="notifications-off">
- <div class="icon-box">
-
- <!-- notifications-off -->
- <svg class="svg-notifications-off-dims">
- <use xlink:href="icons.svg#notifications-off"></use>
- </svg>
-
- </div>
- <h2>notifications-off</h2>
- </li>
- <li title="overview">
- <div class="icon-box">
-
- <!-- overview -->
- <svg class="svg-overview-dims">
- <use xlink:href="icons.svg#overview"></use>
- </svg>
-
- </div>
- <h2>overview</h2>
- </li>
- <li title="pencil">
- <div class="icon-box">
-
- <!-- pencil -->
- <svg class="svg-pencil-dims">
- <use xlink:href="icons.svg#pencil"></use>
- </svg>
-
- </div>
- <h2>pencil</h2>
- </li>
- <li title="pipeline">
- <div class="icon-box">
-
- <!-- pipeline -->
- <svg class="svg-pipeline-dims">
- <use xlink:href="icons.svg#pipeline"></use>
- </svg>
-
- </div>
- <h2>pipeline</h2>
- </li>
- <li title="play">
- <div class="icon-box">
-
- <!-- play -->
- <svg class="svg-play-dims">
- <use xlink:href="icons.svg#play"></use>
- </svg>
-
- </div>
- <h2>play</h2>
- </li>
- <li title="plus">
- <div class="icon-box">
-
- <!-- plus -->
- <svg class="svg-plus-dims">
- <use xlink:href="icons.svg#plus"></use>
- </svg>
-
- </div>
- <h2>plus</h2>
- </li>
- <li title="plus-square">
- <div class="icon-box">
-
- <!-- plus-square -->
- <svg class="svg-plus-square-dims">
- <use xlink:href="icons.svg#plus-square"></use>
- </svg>
-
- </div>
- <h2>plus-square</h2>
- </li>
- <li title="plus-square-o">
- <div class="icon-box">
-
- <!-- plus-square-o -->
- <svg class="svg-plus-square-o-dims">
- <use xlink:href="icons.svg#plus-square-o"></use>
- </svg>
-
- </div>
- <h2>plus-square-o</h2>
- </li>
- <li title="preferences">
- <div class="icon-box">
-
- <!-- preferences -->
- <svg class="svg-preferences-dims">
- <use xlink:href="icons.svg#preferences"></use>
- </svg>
-
- </div>
- <h2>preferences</h2>
- </li>
- <li title="profile">
- <div class="icon-box">
-
- <!-- profile -->
- <svg class="svg-profile-dims">
- <use xlink:href="icons.svg#profile"></use>
- </svg>
-
- </div>
- <h2>profile</h2>
- </li>
- <li title="project">
- <div class="icon-box">
-
- <!-- project -->
- <svg class="svg-project-dims">
- <use xlink:href="icons.svg#project"></use>
- </svg>
-
- </div>
- <h2>project</h2>
- </li>
- <li title="push-rules">
- <div class="icon-box">
-
- <!-- push-rules -->
- <svg class="svg-push-rules-dims">
- <use xlink:href="icons.svg#push-rules"></use>
- </svg>
-
- </div>
- <h2>push-rules</h2>
- </li>
- <li title="question">
- <div class="icon-box">
-
- <!-- question -->
- <svg class="svg-question-dims">
- <use xlink:href="icons.svg#question"></use>
- </svg>
-
- </div>
- <h2>question</h2>
- </li>
- <li title="question-o">
- <div class="icon-box">
-
- <!-- question-o -->
- <svg class="svg-question-o-dims">
- <use xlink:href="icons.svg#question-o"></use>
- </svg>
-
- </div>
- <h2>question-o</h2>
- </li>
- <li title="quote">
- <div class="icon-box">
-
- <!-- quote -->
- <svg class="svg-quote-dims">
- <use xlink:href="icons.svg#quote"></use>
- </svg>
-
- </div>
- <h2>quote</h2>
- </li>
- <li title="redo">
- <div class="icon-box">
-
- <!-- redo -->
- <svg class="svg-redo-dims">
- <use xlink:href="icons.svg#redo"></use>
- </svg>
-
- </div>
- <h2>redo</h2>
- </li>
- <li title="remove">
- <div class="icon-box">
-
- <!-- remove -->
- <svg class="svg-remove-dims">
- <use xlink:href="icons.svg#remove"></use>
- </svg>
-
- </div>
- <h2>remove</h2>
- </li>
- <li title="repeat">
- <div class="icon-box">
-
- <!-- repeat -->
- <svg class="svg-repeat-dims">
- <use xlink:href="icons.svg#repeat"></use>
- </svg>
-
- </div>
- <h2>repeat</h2>
- </li>
- <li title="retry">
- <div class="icon-box">
-
- <!-- retry -->
- <svg class="svg-retry-dims">
- <use xlink:href="icons.svg#retry"></use>
- </svg>
-
- </div>
- <h2>retry</h2>
- </li>
- <li title="scale">
- <div class="icon-box">
-
- <!-- scale -->
- <svg class="svg-scale-dims">
- <use xlink:href="icons.svg#scale"></use>
- </svg>
-
- </div>
- <h2>scale</h2>
- </li>
- <li title="screen-full">
- <div class="icon-box">
-
- <!-- screen-full -->
- <svg class="svg-screen-full-dims">
- <use xlink:href="icons.svg#screen-full"></use>
- </svg>
-
- </div>
- <h2>screen-full</h2>
- </li>
- <li title="screen-normal">
- <div class="icon-box">
-
- <!-- screen-normal -->
- <svg class="svg-screen-normal-dims">
- <use xlink:href="icons.svg#screen-normal"></use>
- </svg>
-
- </div>
- <h2>screen-normal</h2>
- </li>
- <li title="search">
- <div class="icon-box">
-
- <!-- search -->
- <svg class="svg-search-dims">
- <use xlink:href="icons.svg#search"></use>
- </svg>
-
- </div>
- <h2>search</h2>
- </li>
- <li title="settings">
- <div class="icon-box">
-
- <!-- settings -->
- <svg class="svg-settings-dims">
- <use xlink:href="icons.svg#settings"></use>
- </svg>
-
- </div>
- <h2>settings</h2>
- </li>
- <li title="shield">
- <div class="icon-box">
-
- <!-- shield -->
- <svg class="svg-shield-dims">
- <use xlink:href="icons.svg#shield"></use>
- </svg>
-
- </div>
- <h2>shield</h2>
- </li>
- <li title="slight-frown">
- <div class="icon-box">
-
- <!-- slight-frown -->
- <svg class="svg-slight-frown-dims">
- <use xlink:href="icons.svg#slight-frown"></use>
- </svg>
-
- </div>
- <h2>slight-frown</h2>
- </li>
- <li title="slight-smile">
- <div class="icon-box">
-
- <!-- slight-smile -->
- <svg class="svg-slight-smile-dims">
- <use xlink:href="icons.svg#slight-smile"></use>
- </svg>
-
- </div>
- <h2>slight-smile</h2>
- </li>
- <li title="smile">
- <div class="icon-box">
-
- <!-- smile -->
- <svg class="svg-smile-dims">
- <use xlink:href="icons.svg#smile"></use>
- </svg>
-
- </div>
- <h2>smile</h2>
- </li>
- <li title="smiley">
- <div class="icon-box">
-
- <!-- smiley -->
- <svg class="svg-smiley-dims">
- <use xlink:href="icons.svg#smiley"></use>
- </svg>
-
- </div>
- <h2>smiley</h2>
- </li>
- <li title="snippet">
- <div class="icon-box">
-
- <!-- snippet -->
- <svg class="svg-snippet-dims">
- <use xlink:href="icons.svg#snippet"></use>
- </svg>
-
- </div>
- <h2>snippet</h2>
- </li>
- <li title="spam">
- <div class="icon-box">
-
- <!-- spam -->
- <svg class="svg-spam-dims">
- <use xlink:href="icons.svg#spam"></use>
- </svg>
-
- </div>
- <h2>spam</h2>
- </li>
- <li title="star">
- <div class="icon-box">
-
- <!-- star -->
- <svg class="svg-star-dims">
- <use xlink:href="icons.svg#star"></use>
- </svg>
-
- </div>
- <h2>star</h2>
- </li>
- <li title="star-o">
- <div class="icon-box">
-
- <!-- star-o -->
- <svg class="svg-star-o-dims">
- <use xlink:href="icons.svg#star-o"></use>
- </svg>
-
- </div>
- <h2>star-o</h2>
- </li>
- <li title="stop">
- <div class="icon-box">
-
- <!-- stop -->
- <svg class="svg-stop-dims">
- <use xlink:href="icons.svg#stop"></use>
- </svg>
-
- </div>
- <h2>stop</h2>
- </li>
- <li title="talic">
- <div class="icon-box">
-
- <!-- talic -->
- <svg class="svg-talic-dims">
- <use xlink:href="icons.svg#talic"></use>
- </svg>
-
- </div>
- <h2>talic</h2>
- </li>
- <li title="task-done">
- <div class="icon-box">
-
- <!-- task-done -->
- <svg class="svg-task-done-dims">
- <use xlink:href="icons.svg#task-done"></use>
- </svg>
-
- </div>
- <h2>task-done</h2>
- </li>
- <li title="template">
- <div class="icon-box">
-
- <!-- template -->
- <svg class="svg-template-dims">
- <use xlink:href="icons.svg#template"></use>
- </svg>
-
- </div>
- <h2>template</h2>
- </li>
- <li title="thump-down">
- <div class="icon-box">
-
- <!-- thump-down -->
- <svg class="svg-thump-down-dims">
- <use xlink:href="icons.svg#thump-down"></use>
- </svg>
-
- </div>
- <h2>thump-down</h2>
- </li>
- <li title="thump-up">
- <div class="icon-box">
-
- <!-- thump-up -->
- <svg class="svg-thump-up-dims">
- <use xlink:href="icons.svg#thump-up"></use>
- </svg>
-
- </div>
- <h2>thump-up</h2>
- </li>
- <li title="timer">
- <div class="icon-box">
-
- <!-- timer -->
- <svg class="svg-timer-dims">
- <use xlink:href="icons.svg#timer"></use>
- </svg>
-
- </div>
- <h2>timer</h2>
- </li>
- <li title="todo-add">
- <div class="icon-box">
-
- <!-- todo-add -->
- <svg class="svg-todo-add-dims">
- <use xlink:href="icons.svg#todo-add"></use>
- </svg>
-
- </div>
- <h2>todo-add</h2>
- </li>
- <li title="todo-done">
- <div class="icon-box">
-
- <!-- todo-done -->
- <svg class="svg-todo-done-dims">
- <use xlink:href="icons.svg#todo-done"></use>
- </svg>
-
- </div>
- <h2>todo-done</h2>
- </li>
- <li title="token">
- <div class="icon-box">
-
- <!-- token -->
- <svg class="svg-token-dims">
- <use xlink:href="icons.svg#token"></use>
- </svg>
-
- </div>
- <h2>token</h2>
- </li>
- <li title="unapproval">
- <div class="icon-box">
-
- <!-- unapproval -->
- <svg class="svg-unapproval-dims">
- <use xlink:href="icons.svg#unapproval"></use>
- </svg>
-
- </div>
- <h2>unapproval</h2>
- </li>
- <li title="unassignee">
- <div class="icon-box">
-
- <!-- unassignee -->
- <svg class="svg-unassignee-dims">
- <use xlink:href="icons.svg#unassignee"></use>
- </svg>
-
- </div>
- <h2>unassignee</h2>
- </li>
- <li title="unlink">
- <div class="icon-box">
-
- <!-- unlink -->
- <svg class="svg-unlink-dims">
- <use xlink:href="icons.svg#unlink"></use>
- </svg>
-
- </div>
- <h2>unlink</h2>
- </li>
- <li title="user">
- <div class="icon-box">
-
- <!-- user -->
- <svg class="svg-user-dims">
- <use xlink:href="icons.svg#user"></use>
- </svg>
-
- </div>
- <h2>user</h2>
- </li>
- <li title="users">
- <div class="icon-box">
-
- <!-- users -->
- <svg class="svg-users-dims">
- <use xlink:href="icons.svg#users"></use>
- </svg>
-
- </div>
- <h2>users</h2>
- </li>
- <li title="volume-up">
- <div class="icon-box">
-
- <!-- volume-up -->
- <svg class="svg-volume-up-dims">
- <use xlink:href="icons.svg#volume-up"></use>
- </svg>
-
- </div>
- <h2>volume-up</h2>
- </li>
- <li title="warning">
- <div class="icon-box">
-
- <!-- warning -->
- <svg class="svg-warning-dims">
- <use xlink:href="icons.svg#warning"></use>
- </svg>
-
- </div>
- <h2>warning</h2>
- </li>
- <li title="work">
- <div class="icon-box">
-
- <!-- work -->
- <svg class="svg-work-dims">
- <use xlink:href="icons.svg#work"></use>
- </svg>
-
- </div>
- <h2>work</h2>
- </li>
- </ul>
-
-<!--
-====================================================================================================
--->
-
- </section>
- <footer>
- <p>Generated at Tue, 12 Sep 2017 09:08:46 GMT by <a href="https://github.com/jkphl/svg-sprite" target="_blank">svg-sprite</a>.</p>
- </footer>
- </body>
-</html>
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
index 8f5e2e545ec..2bc77859c26 100644
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -1,4 +1,4 @@
-class AjaxLoadingSpinner {
+export default class AjaxLoadingSpinner {
static init() {
const $elements = $('.js-ajax-loading-spinner');
@@ -30,6 +30,3 @@ class AjaxLoadingSpinner {
classList.toggle('fa-spin');
}
}
-
-window.gl = window.gl || {};
-gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
index 27312d718b0..c858a6bb7b4 100644
--- a/app/assets/javascripts/blob/notebook/index.js
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -40,10 +40,10 @@ export default () => {
class="text-center"
v-if="error">
<span v-if="loadError">
- An error occured whilst loading the file. Please try again later.
+ An error occurred whilst loading the file. Please try again later.
</span>
<span v-else>
- An error occured whilst parsing the file.
+ An error occurred whilst parsing the file.
</span>
</p>
</div>
diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js
index 0ed915c1ac9..7109f356540 100644
--- a/app/assets/javascripts/blob/pdf/index.js
+++ b/app/assets/javascripts/blob/pdf/index.js
@@ -48,10 +48,10 @@ export default () => {
class="text-center"
v-if="error">
<span v-if="loadError">
- An error occured whilst loading the file. Please try again later.
+ An error occurred whilst loading the file. Please try again later.
</span>
<span v-else>
- An error occured whilst decoding the file.
+ An error occurred whilst decoding the file.
</span>
</p>
</div>
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index ea00efe4b46..815248f38ee 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -77,9 +77,6 @@ $(() => {
});
Store.rootPath = this.boardsEndpoint;
- this.filterManager = new FilteredSearchBoards(Store.filter, true);
- this.filterManager.setup();
-
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
},
@@ -87,6 +84,9 @@ $(() => {
eventHub.$off('updateTokens', this.updateTokens);
},
mounted () {
+ this.filterManager = new FilteredSearchBoards(Store.filter, true);
+ this.filterManager.setup();
+
Store.disabled = this.disabled;
gl.boardService.all()
.then(response => response.json())
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js
index 541b8049855..bc28f7f45f4 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.js
+++ b/app/assets/javascripts/boards/components/board_new_issue.js
@@ -68,7 +68,7 @@ export default {
<div class="flash-container"
v-if="error">
<div class="flash-alert">
- An error occured. Please try again.
+ An error occurred. Please try again.
</div>
</div>
<label class="label-light"
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index d7f203b3f96..c19c989680d 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,6 +1,7 @@
-/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var,
+/* eslint-disable func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */
import _ from 'underscore';
+import CreateLabelDropdown from '../../create_label';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@@ -15,15 +16,15 @@ $(document).off('created.label').on('created.label', (e, label) => {
label: {
id: label.id,
title: label.title,
- color: label.color
- }
+ color: label.color,
+ },
});
});
gl.issueBoards.newListDropdownInit = () => {
$('.js-new-board-list').each(function () {
const $this = $(this);
- new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
+ new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
$this.glDropdown({
data(term, callback) {
@@ -38,17 +39,17 @@ gl.issueBoards.newListDropdownInit = () => {
const $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
text: label.title,
- href: '#'
+ href: '#',
});
const $labelColor = $('<span />', {
class: 'dropdown-label-box',
- style: `background-color: ${label.color}`
+ style: `background-color: ${label.color}`,
});
return $li.append($a.prepend($labelColor));
},
search: {
- fields: ['title']
+ fields: ['title'],
},
filterable: true,
selectable: true,
@@ -66,13 +67,13 @@ gl.issueBoards.newListDropdownInit = () => {
label: {
id: label.id,
title: label.title,
- color: label.color
- }
+ color: label.color,
+ },
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
}
- }
+ },
});
});
};
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 286a758b8a9..3d27a3544eb 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -167,7 +167,7 @@ window.Build = (function () {
Build.prototype.getBuildTrace = function () {
return $.ajax({
url: `${this.pageUrl}/trace.json`,
- data: this.state,
+ data: { state: this.state },
})
.done((log) => {
setCiStatusFavicon(`${this.pageUrl}/status.json`);
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index bd479700fd3..19388f1f9ae 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,9 +1,12 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */
+import { visitUrl } from './lib/utils/url_utility';
+import { convertPermissionToBoolean } from './lib/utils/common_utils';
window.BuildArtifacts = (function() {
function BuildArtifacts() {
this.disablePropagation();
this.setupEntryClick();
+ this.setupTooltips();
}
BuildArtifacts.prototype.disablePropagation = function() {
@@ -17,9 +20,28 @@ window.BuildArtifacts = (function() {
BuildArtifacts.prototype.setupEntryClick = function() {
return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
- return window.location = this.dataset.link;
+ visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
});
};
+ BuildArtifacts.prototype.setupTooltips = function() {
+ $('.js-artifact-tree-tooltip').tooltip({
+ placement: 'bottom',
+ // Stop the tooltip from hiding when we stop hovering the element directly
+ // We handle all the showing/hiding below
+ trigger: 'manual',
+ });
+
+ // We want the tooltip to show if you hover anywhere on the row
+ // But be placed below and in the middle of the file name
+ $('.js-artifact-tree-row')
+ .on('mouseenter', (e) => {
+ $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('show');
+ })
+ .on('mouseleave', (e) => {
+ $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide');
+ });
+ };
+
return BuildArtifacts;
})();
diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js
new file mode 100644
index 00000000000..50dbeb06362
--- /dev/null
+++ b/app/assets/javascripts/clusters.js
@@ -0,0 +1,112 @@
+/* globals Flash */
+import Visibility from 'visibilityjs';
+import axios from 'axios';
+import Poll from './lib/utils/poll';
+import { s__ } from './locale';
+import './flash';
+
+/**
+ * Cluster page has 2 separate parts:
+ * Toggle button
+ *
+ * - Polling status while creating or scheduled
+ * -- Update status area with the response result
+ */
+
+class ClusterService {
+ constructor(options = {}) {
+ this.options = options;
+ }
+ fetchData() {
+ return axios.get(this.options.endpoint);
+ }
+}
+
+export default class Clusters {
+ constructor() {
+ const dataset = document.querySelector('.js-edit-cluster-form').dataset;
+
+ this.state = {
+ statusPath: dataset.statusPath,
+ clusterStatus: dataset.clusterStatus,
+ clusterStatusReason: dataset.clusterStatusReason,
+ toggleStatus: dataset.toggleStatus,
+ };
+
+ this.service = new ClusterService({ endpoint: this.state.statusPath });
+ this.toggleButton = document.querySelector('.js-toggle-cluster');
+ this.toggleInput = document.querySelector('.js-toggle-input');
+ this.errorContainer = document.querySelector('.js-cluster-error');
+ this.successContainer = document.querySelector('.js-cluster-success');
+ this.creatingContainer = document.querySelector('.js-cluster-creating');
+ this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
+
+ this.toggleButton.addEventListener('click', this.toggle.bind(this));
+
+ if (this.state.clusterStatus !== 'created') {
+ this.updateContainer(this.state.clusterStatus, this.state.clusterStatusReason);
+ }
+
+ if (this.state.statusPath) {
+ this.initPolling();
+ }
+ }
+
+ toggle() {
+ this.toggleButton.classList.toggle('checked');
+ this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('checked').toString());
+ }
+
+ initPolling() {
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'fetchData',
+ successCallback: (data) => {
+ const { status, status_reason } = data.data;
+ this.updateContainer(status, status_reason);
+ },
+ errorCallback: () => {
+ Flash(s__('ClusterIntegration|Something went wrong on our end.'));
+ },
+ });
+
+ if (!Visibility.hidden()) {
+ this.poll.makeRequest();
+ } else {
+ this.service.fetchData();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ this.poll.restart();
+ } else {
+ this.poll.stop();
+ }
+ });
+ }
+
+ hideAll() {
+ this.errorContainer.classList.add('hidden');
+ this.successContainer.classList.add('hidden');
+ this.creatingContainer.classList.add('hidden');
+ }
+
+ updateContainer(status, error) {
+ this.hideAll();
+ switch (status) {
+ case 'created':
+ this.successContainer.classList.remove('hidden');
+ break;
+ case 'errored':
+ this.errorContainer.classList.remove('hidden');
+ this.errorReasonContainer.textContent = error;
+ break;
+ case 'scheduled':
+ case 'creating':
+ this.creatingContainer.classList.remove('hidden');
+ break;
+ default:
+ this.hideAll();
+ }
+ }
+}
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
deleted file mode 100644
index 5f637524e30..00000000000
--- a/app/assets/javascripts/commit.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife */
-/* global CommitFile */
-
-window.Commit = (function() {
- function Commit() {
- $('.files .diff-file').each(function() {
- return new CommitFile(this);
- });
- }
-
- return Commit;
-})();
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
deleted file mode 100644
index ee087c978dd..00000000000
--- a/app/assets/javascripts/commit/file.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */
-/* global ImageFile */
-
-(function() {
- this.CommitFile = (function() {
- function CommitFile(file) {
- if ($('.image', file).length) {
- new gl.ImageFile(file);
- }
- }
-
- return CommitFile;
- })();
-}).call(window);
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 4763985c802..e7adf8814b8 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
+import 'vendor/jquery.waitforimages';
+
(function() {
gl.ImageFile = (function() {
var prepareFrames;
@@ -17,15 +19,10 @@
// Load two-up view after images are loaded
// so that we can display the correct width and height information
- const images = $('.two-up.view img', _this.file);
- let loadedCount = 0;
-
- images.on('load', () => {
- loadedCount += 1;
+ const $images = $('.two-up.view img', _this.file);
- if (loadedCount === images.length) {
- _this.initView('two-up');
- }
+ $images.waitForImages(function() {
+ _this.initView('two-up');
});
});
};
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index e3e2c798570..93b0cbf4209 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -298,7 +298,7 @@ class CopyAsGFM {
const documentFragment = getSelectedFragment();
if (!documentFragment) return;
- const el = transformer(documentFragment.cloneNode(true));
+ const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
if (!el) return;
e.preventDefault();
@@ -338,55 +338,64 @@ class CopyAsGFM {
}
static transformGFMSelection(documentFragment) {
- const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
- switch (gfmEls.length) {
+ const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
+ switch (gfmElements.length) {
case 0: {
return documentFragment;
}
case 1: {
- return gfmEls[0];
+ return gfmElements[0];
}
default: {
- const allGfmEl = document.createElement('div');
+ const allGfmElement = document.createElement('div');
- for (let i = 0; i < gfmEls.length; i += 1) {
- const lineEl = gfmEls[i];
- allGfmEl.appendChild(lineEl);
- allGfmEl.appendChild(document.createTextNode('\n\n'));
+ for (let i = 0; i < gfmElements.length; i += 1) {
+ const gfmElement = gfmElements[i];
+ allGfmElement.appendChild(gfmElement);
+ allGfmElement.appendChild(document.createTextNode('\n\n'));
}
- return allGfmEl;
+ return allGfmElement;
}
}
}
- static transformCodeSelection(documentFragment) {
- const lineEls = documentFragment.querySelectorAll('.line');
+ static transformCodeSelection(documentFragment, target) {
+ let lineSelector = '.line';
- let codeEl;
- if (lineEls.length > 1) {
- codeEl = document.createElement('pre');
- codeEl.className = 'code highlight';
+ if (target) {
+ const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
+ if (lineClass) {
+ lineSelector = `.line_content.${lineClass} ${lineSelector}`;
+ }
+ }
+
+ const lineElements = documentFragment.querySelectorAll(lineSelector);
+
+ let codeElement;
+ if (lineElements.length > 1) {
+ codeElement = document.createElement('pre');
+ codeElement.className = 'code highlight';
- const lang = lineEls[0].getAttribute('lang');
+ const lang = lineElements[0].getAttribute('lang');
if (lang) {
- codeEl.setAttribute('lang', lang);
+ codeElement.setAttribute('lang', lang);
}
} else {
- codeEl = document.createElement('code');
+ codeElement = document.createElement('code');
}
- if (lineEls.length > 0) {
- for (let i = 0; i < lineEls.length; i += 1) {
- const lineEl = lineEls[i];
- codeEl.appendChild(lineEl);
- codeEl.appendChild(document.createTextNode('\n'));
+ if (lineElements.length > 0) {
+ for (let i = 0; i < lineElements.length; i += 1) {
+ const lineElement = lineElements[i];
+ codeElement.appendChild(lineElement);
+ codeElement.appendChild(document.createTextNode('\n'));
}
} else {
- codeEl.appendChild(documentFragment);
+ codeElement.appendChild(documentFragment);
}
- return codeEl;
+ return codeElement;
}
static nodeToGFM(node, respectWhitespaceParam = false) {
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index 907b468e576..3bed0678350 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
+/* eslint-disable func-names, prefer-arrow-callback */
import Api from './api';
-class CreateLabelDropdown {
- constructor ($el, namespacePath, projectPath) {
+export default class CreateLabelDropdown {
+ constructor($el, namespacePath, projectPath) {
this.$el = $el;
this.namespacePath = namespacePath;
this.projectPath = projectPath;
@@ -22,7 +22,7 @@ class CreateLabelDropdown {
this.addBinding();
}
- cleanBinding () {
+ cleanBinding() {
this.$colorSuggestions.off('click');
this.$newLabelField.off('keyup change');
this.$newColorField.off('keyup change');
@@ -31,7 +31,7 @@ class CreateLabelDropdown {
this.$newLabelCreateButton.off('click');
}
- addBinding () {
+ addBinding() {
const self = this;
this.$colorSuggestions.on('click', function (e) {
@@ -44,7 +44,7 @@ class CreateLabelDropdown {
this.$dropdownBack.on('click', this.resetForm.bind(this));
- this.$cancelButton.on('click', function(e) {
+ this.$cancelButton.on('click', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -55,7 +55,7 @@ class CreateLabelDropdown {
this.$newLabelCreateButton.on('click', this.saveLabel.bind(this));
}
- addColorValue (e, $this) {
+ addColorValue(e, $this) {
e.preventDefault();
e.stopPropagation();
@@ -66,7 +66,7 @@ class CreateLabelDropdown {
.addClass('is-active');
}
- enableLabelCreateButton () {
+ enableLabelCreateButton() {
if (this.$newLabelField.val() !== '' && this.$newColorField.val() !== '') {
this.$newLabelError.hide();
this.$newLabelCreateButton.enable();
@@ -75,7 +75,7 @@ class CreateLabelDropdown {
}
}
- resetForm () {
+ resetForm() {
this.$newLabelField
.val('')
.trigger('change');
@@ -90,13 +90,13 @@ class CreateLabelDropdown {
.removeClass('is-active');
}
- saveLabel (e) {
+ saveLabel(e) {
e.preventDefault();
e.stopPropagation();
Api.newLabel(this.namespacePath, this.projectPath, {
title: this.$newLabelField.val(),
- color: this.$newColorField.val()
+ color: this.$newColorField.val(),
}, (label) => {
this.$newLabelCreateButton.enable();
@@ -107,8 +107,8 @@ class CreateLabelDropdown {
errors = label.message;
} else {
errors = Object.keys(label.message).map(key =>
- `${gl.text.humanize(key)} ${label.message[key].join(', ')}`
- ).join("<br/>");
+ `${gl.text.humanize(key)} ${label.message[key].join(', ')}`,
+ ).join('<br/>');
}
this.$newLabelError
@@ -122,6 +122,3 @@ class CreateLabelDropdown {
});
}
}
-
-window.gl = window.gl || {};
-gl.CreateLabelDropdown = CreateLabelDropdown;
diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue
new file mode 100644
index 00000000000..732697c134e
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/banner.vue
@@ -0,0 +1,55 @@
+<script>
+ import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
+
+ export default {
+ props: {
+ documentationLink: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ iconCycleAnalyticsSplash() {
+ return iconCycleAnalyticsSplash;
+ },
+ },
+ methods: {
+ dismissOverviewDialog() {
+ this.$emit('dismiss-overview-dialog');
+ },
+ },
+ };
+</script>
+<template>
+ <div class="landing content-block">
+ <button
+ class="js-ca-dismiss-button dismiss-button"
+ type="button"
+ :aria-label="__('Dismiss Cycle Analytics introduction box')"
+ @click="dismissOverviewDialog">
+ <i
+ class="fa fa-times"
+ aria-hidden="true">
+ </i>
+ </button>
+ <div class="svg-container" v-html="iconCycleAnalyticsSplash">
+ </div>
+ <div class="inner-content">
+ <h4>
+ {{__('Introducing Cycle Analytics')}}
+ </h4>
+ <p>
+ {{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
+ </p>
+ <p>
+ <a
+ :href="documentationLink"
+ target="_blank"
+ rel="nofollow"
+ class="btn">
+ {{__('Read more')}}
+ </a>
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
index e4d62b649e5..45930145b0a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
@@ -1,5 +1,7 @@
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
export default {
props: {
@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
+ limitWarning,
+ totalTime,
},
};
</script>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
index ab730af8f5b..8c98bd249a1 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
@@ -1,5 +1,7 @@
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
export default {
props: {
@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
+ limitWarning,
+ totalTime,
},
};
</script>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
index 152c086a606..75d2f1fd70c 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
@@ -1,21 +1,25 @@
<script>
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-import iconCommit from '../svg/icon_commit.svg';
+ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import iconCommit from '../svg/icon_commit.svg';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
-export default {
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- computed: {
- iconCommit() {
- return iconCommit;
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
},
- },
-};
+ components: {
+ userAvatarImage,
+ totalTime,
+ limitWarning,
+ },
+ computed: {
+ iconCommit() {
+ return iconCommit;
+ },
+ },
+ };
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
index 9e66b690404..f54ea7df522 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -1,5 +1,7 @@
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
export default {
props: {
@@ -8,6 +10,8 @@
},
components: {
userAvatarImage,
+ totalTime,
+ limitWarning,
},
};
</script>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
index 2787b5ea47b..5d95ddcd90e 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -1,6 +1,8 @@
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
export default {
props: {
@@ -9,6 +11,8 @@
},
components: {
userAvatarImage,
+ totalTime,
+ limitWarning,
},
computed: {
iconBranch() {
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
index 9c3d39ce011..04d5440b77b 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -1,21 +1,27 @@
<script>
-import iconBuildStatus from '../svg/icon_build_status.svg';
-import iconBranch from '../svg/icon_branch.svg';
+ import iconBuildStatus from '../svg/icon_build_status.svg';
+ import iconBranch from '../svg/icon_branch.svg';
+ import limitWarning from './limit_warning_component.vue';
+ import totalTime from './total_time_component.vue';
-export default {
- props: {
- items: Array,
- stage: Object,
- },
- computed: {
- iconBuildStatus() {
- return iconBuildStatus;
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
},
- iconBranch() {
- return iconBranch;
+ components: {
+ totalTime,
+ limitWarning,
},
- },
-};
+ computed: {
+ iconBuildStatus() {
+ return iconBuildStatus;
+ },
+ iconBranch() {
+ return iconBranch;
+ },
+ },
+ };
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
index 9941b997b3f..62efd4f9c28 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
@@ -20,7 +20,7 @@
<template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
<template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
<template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
- <template v-if="time.seconds && hasDa === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
+ <template v-if="time.seconds && hasData === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
</template>
<template v-else>
--
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 8002b0b23c9..cdf5e3c0290 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -3,14 +3,13 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate';
-import limitWarningComponent from './components/limit_warning_component.vue';
+import banner from './components/banner.vue';
import stageCodeComponent from './components/stage_code_component.vue';
import stagePlanComponent from './components/stage_plan_component.vue';
import stageComponent from './components/stage_component.vue';
import stageReviewComponent from './components/stage_review_component.vue';
import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue';
-import totalTime from './components/total_time_component.vue';
import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store';
@@ -46,6 +45,7 @@ $(() => {
},
},
components: {
+ banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent,
@@ -133,8 +133,4 @@ $(() => {
},
},
});
-
- // Register global components
- Vue.component('limit-warning', limitWarningComponent);
- Vue.component('total-time', totalTime);
});
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 6a008112203..6c78662baa7 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -3,6 +3,7 @@
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
+import imageDiffHelper from './image_diff/helpers/index';
const UNFOLD_COUNT = 20;
let isBound = false;
@@ -17,14 +18,18 @@ class Diff {
}
});
- FilesCommentButton.init($diffFile);
+ const tab = document.getElementById('diffs');
+ if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile);
- $diffFile.each((index, file) => new gl.ImageFile(file));
+ const firstFile = $('.files').first().get(0);
+ const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
+ $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote));
if (!isBound) {
$(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
- .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
+ .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this))
+ .on('mousedown', 'td.line_content.parallel', this.handleParallelLineDown.bind(this));
isBound = true;
}
@@ -100,6 +105,18 @@ class Diff {
this.highlightSelectedLine();
}
+ handleParallelLineDown(e) {
+ const line = $(e.currentTarget);
+ const table = line.closest('table');
+
+ table.removeClass('left-side-selected right-side-selected');
+
+ const lineClass = ['left-side', 'right-side'].filter(name => line.hasClass(name))[0];
+ if (lineClass) {
+ table.addClass(`${lineClass}-selected`);
+ }
+ }
+
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index 497c23f014f..e77910a83d4 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -171,7 +171,14 @@ const JumpToDiscussion = Vue.extend({
// When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show();
- $target = $target.closest("tr.notes_holder");
+ const $notesHolder = $target.closest("tr.notes_holder");
+
+ // Image diff discussions does not use notes_holder
+ // so we should keep original $target value in those cases
+ if ($notesHolder.length > 0) {
+ $target = $notesHolder;
+ }
+
$target.show();
// If we are on the diffs tab, we don't scroll to the discussion itself, but to
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 31214818496..6a4dce20f24 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -7,14 +7,12 @@
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
-/* global Commit */
/* global CommitsList */
/* global NewBranchForm */
/* global NotificationsForm */
/* global NotificationsDropdown */
/* global GroupAvatar */
/* global LineHighlighter */
-/* global ProjectFork */
/* global BuildArtifacts */
/* global GroupsSelect */
/* global Search */
@@ -78,6 +76,7 @@ import GpgBadges from './gpg_badges';
import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
+import AjaxLoadingSpinner from './ajax_loading_spinner';
(function() {
var Dispatcher;
@@ -239,7 +238,7 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
break;
case 'projects:branches:index':
- gl.AjaxLoadingSpinner.init();
+ AjaxLoadingSpinner.init();
new DeleteModal();
break;
case 'projects:issues:new':
@@ -317,7 +316,6 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new gl.Activities();
break;
case 'projects:commit:show':
- new Commit();
new gl.Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
@@ -476,7 +474,9 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
shortcut_handler = true;
break;
case 'projects:forks:new':
- new ProjectFork();
+ import(/* webpackChunkName: 'project_fork' */ './project_fork')
+ .then(fork => fork.default())
+ .catch(() => {});
break;
case 'projects:artifacts:browse':
new ShortcutsNavigation();
@@ -524,6 +524,11 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
case 'admin:impersonation_tokens:index':
new gl.DueDateSelectors();
break;
+ case 'projects:clusters:show':
+ import(/* webpackChunkName: "clusters" */ './clusters')
+ .then(cluster => new cluster.default()) // eslint-disable-line new-cap
+ .catch(() => {});
+ break;
}
switch (path[0]) {
case 'sessions':
diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue
index 14fde1afb16..ce5f6219a3e 100644
--- a/app/assets/javascripts/environments/components/environment.vue
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -163,7 +163,7 @@ export default {
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
- .catch(() => new Flash('An error occured while making the request.'));
+ .catch(() => new Flash('An error occurred while making the request.'));
}
},
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 35891240239..01e70c0bbb7 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -158,7 +158,7 @@ export default {
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
- .catch(() => new Flash('An error occured while making the request.'));
+ .catch(() => new Flash('An error occurred while making the request.'));
}
},
},
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index d02e4cd5876..a00d29a845a 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -7,6 +7,8 @@
* causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/
+import Cookies from 'js-cookie';
+
const LINE_NUMBER_CLASS = 'diff-line-num';
const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn';
@@ -27,9 +29,7 @@ export default {
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
}
- if (typeof notes !== 'undefined' && !this.isParallelView) {
- this.isParallelView = notes.isParallelView && notes.isParallelView();
- }
+ this.isParallelView = Cookies.get('diff_view') === 'parallel';
if (this.userCanCreateNote) {
$diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js
index f9bbbf0cbc1..ada14d2053c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_emoji.js
+++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js
@@ -14,7 +14,7 @@ class DropdownEmoji extends gl.FilteredSearchDropdown {
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
- new Flash('An error occured fetching the dropdown data.');
+ new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */
},
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 0bc4b6f22a9..b32d589481d 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -17,7 +17,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
preprocessing,
onError() {
/* eslint-disable no-new */
- new Flash('An error occured fetching the dropdown data.');
+ new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */
},
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 720fbc87ea0..ce8817b1b2e 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -26,7 +26,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
},
onError() {
/* eslint-disable no-new */
- new Flash('An error occured fetching the dropdown data.');
+ new Flash('An error occurred fetching the dropdown data.');
/* eslint-enable no-new */
},
},
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 9178fec085a..a44dc279a6f 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -36,7 +36,7 @@ class FilteredSearchManager {
.catch((error) => {
if (error.name === 'RecentSearchesServiceError') return undefined;
// eslint-disable-next-line no-new
- new window.Flash('An error occured while parsing recent searches');
+ new window.Flash('An error occurred while parsing recent searches');
// Gracefully fail to empty array
return [];
})
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 50d822eba5a..e8d8fef8579 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -548,6 +548,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.positionMenuAbove = function() {
var $menu = this.dropdown.find('.dropdown-menu');
+ $menu.addClass('dropdown-open-top');
$menu.css('top', 'initial');
$menu.css('bottom', '100%');
};
@@ -737,7 +738,7 @@ GitLabDropdown = (function() {
: selectedObject.id;
if (isInput) {
field = $(this.el);
- } else if (value) {
+ } else if (value != null) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
}
@@ -745,7 +746,7 @@ GitLabDropdown = (function() {
return;
}
- if (el.hasClass(ACTIVE_CLASS)) {
+ if (el.hasClass(ACTIVE_CLASS) && value !== 0) {
isMarking = false;
el.removeClass(ACTIVE_CLASS);
if (field && field.length) {
@@ -851,7 +852,7 @@ GitLabDropdown = (function() {
if (href && href !== '#') {
gl.utils.visitUrl(href);
} else {
- $el.first().trigger('click');
+ $el.trigger('click');
}
}
};
diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js
new file mode 100644
index 00000000000..6a6a668308d
--- /dev/null
+++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js
@@ -0,0 +1,38 @@
+export function createImageBadge(noteId, { x, y }, classNames = []) {
+ const buttonEl = document.createElement('button');
+ const classList = classNames.concat(['js-image-badge']);
+ classList.forEach(className => buttonEl.classList.add(className));
+ buttonEl.setAttribute('type', 'button');
+ buttonEl.setAttribute('disabled', true);
+ buttonEl.dataset.noteId = noteId;
+ buttonEl.style.left = `${x}px`;
+ buttonEl.style.top = `${y}px`;
+
+ return buttonEl;
+}
+
+export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
+ const buttonEl = createImageBadge(noteId, coordinate, ['badge']);
+ buttonEl.innerText = badgeText;
+
+ containerEl.appendChild(buttonEl);
+}
+
+export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
+ const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']);
+ const iconEl = document.createElement('i');
+ iconEl.className = 'fa fa-comment-o';
+ iconEl.setAttribute('aria-label', 'comment');
+
+ buttonEl.appendChild(iconEl);
+ containerEl.appendChild(buttonEl);
+}
+
+export function addAvatarBadge(el, event) {
+ const { noteId, badgeNumber } = event.detail;
+
+ // Add badge to new comment
+ const avatarBadgeEl = el.querySelector(`#${noteId} .badge`);
+ avatarBadgeEl.innerText = badgeNumber;
+ avatarBadgeEl.classList.remove('hidden');
+}
diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
new file mode 100644
index 00000000000..05000c73052
--- /dev/null
+++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
@@ -0,0 +1,58 @@
+export function addCommentIndicator(containerEl, { x, y }) {
+ const buttonEl = document.createElement('button');
+ buttonEl.classList.add('btn-transparent');
+ buttonEl.classList.add('comment-indicator');
+ buttonEl.setAttribute('type', 'button');
+ buttonEl.style.left = `${x}px`;
+ buttonEl.style.top = `${y}px`;
+
+ buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
+
+ containerEl.appendChild(buttonEl);
+}
+
+export function removeCommentIndicator(imageFrameEl) {
+ const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
+ const imageEl = imageFrameEl.querySelector('img');
+ const willRemove = !!commentIndicatorEl;
+ let meta = {};
+
+ if (willRemove) {
+ meta = {
+ x: parseInt(commentIndicatorEl.style.left, 10),
+ y: parseInt(commentIndicatorEl.style.top, 10),
+ image: {
+ width: imageEl.width,
+ height: imageEl.height,
+ },
+ };
+
+ commentIndicatorEl.remove();
+ }
+
+ return Object.assign({}, meta, {
+ removed: willRemove,
+ });
+}
+
+export function showCommentIndicator(imageFrameEl, coordinate) {
+ const { x, y } = coordinate;
+ const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
+
+ if (commentIndicatorEl) {
+ commentIndicatorEl.style.left = `${x}px`;
+ commentIndicatorEl.style.top = `${y}px`;
+ } else {
+ addCommentIndicator(imageFrameEl, coordinate);
+ }
+}
+
+export function commentIndicatorOnClick(event) {
+ // Prevent from triggering onAddImageDiffNote in notes.js
+ event.stopPropagation();
+
+ const buttonEl = event.currentTarget;
+ const diffViewerEl = buttonEl.closest('.diff-viewer');
+ const textareaEl = diffViewerEl.querySelector('.note-container .note-textarea');
+ textareaEl.focus();
+}
diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js
new file mode 100644
index 00000000000..12d56714b34
--- /dev/null
+++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js
@@ -0,0 +1,44 @@
+export function setPositionDataAttribute(el, options) {
+ // Update position data attribute so that the
+ // new comment form can use this data for ajax request
+ const { x, y, width, height } = options;
+ const position = el.dataset.position;
+ const positionObject = Object.assign({}, JSON.parse(position), {
+ x,
+ y,
+ width,
+ height,
+ });
+
+ el.setAttribute('data-position', JSON.stringify(positionObject));
+}
+
+export function updateDiscussionAvatarBadgeNumber(discussionEl, newBadgeNumber) {
+ const avatarBadgeEl = discussionEl.querySelector('.image-diff-avatar-link .badge');
+ avatarBadgeEl.innerText = newBadgeNumber;
+}
+
+export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) {
+ const discussionBadgeEl = discussionEl.querySelector('.badge');
+ discussionBadgeEl.innerText = newBadgeNumber;
+}
+
+export function toggleCollapsed(event) {
+ const toggleButtonEl = event.currentTarget;
+ const discussionNotesEl = toggleButtonEl.closest('.discussion-notes');
+ const formEl = discussionNotesEl.querySelector('.discussion-form');
+ const isCollapsed = discussionNotesEl.classList.contains('collapsed');
+
+ if (isCollapsed) {
+ discussionNotesEl.classList.remove('collapsed');
+ } else {
+ discussionNotesEl.classList.add('collapsed');
+ }
+
+ // Override the inline display style set in notes.js
+ if (formEl && !isCollapsed) {
+ formEl.style.display = 'none';
+ } else if (formEl && isCollapsed) {
+ formEl.style.display = 'block';
+ }
+}
diff --git a/app/assets/javascripts/image_diff/helpers/index.js b/app/assets/javascripts/image_diff/helpers/index.js
new file mode 100644
index 00000000000..4a100631003
--- /dev/null
+++ b/app/assets/javascripts/image_diff/helpers/index.js
@@ -0,0 +1,25 @@
+import * as badgeHelper from './badge_helper';
+import * as commentIndicatorHelper from './comment_indicator_helper';
+import * as domHelper from './dom_helper';
+import * as utilsHelper from './utils_helper';
+
+export default {
+ addCommentIndicator: commentIndicatorHelper.addCommentIndicator,
+ removeCommentIndicator: commentIndicatorHelper.removeCommentIndicator,
+ showCommentIndicator: commentIndicatorHelper.showCommentIndicator,
+ commentIndicatorOnClick: commentIndicatorHelper.commentIndicatorOnClick,
+
+ addImageBadge: badgeHelper.addImageBadge,
+ addImageCommentBadge: badgeHelper.addImageCommentBadge,
+ addAvatarBadge: badgeHelper.addAvatarBadge,
+
+ setPositionDataAttribute: domHelper.setPositionDataAttribute,
+ updateDiscussionAvatarBadgeNumber: domHelper.updateDiscussionAvatarBadgeNumber,
+ updateDiscussionBadgeNumber: domHelper.updateDiscussionBadgeNumber,
+ toggleCollapsed: domHelper.toggleCollapsed,
+
+ resizeCoordinatesToImageElement: utilsHelper.resizeCoordinatesToImageElement,
+ generateBadgeFromDiscussionDOM: utilsHelper.generateBadgeFromDiscussionDOM,
+ getTargetSelection: utilsHelper.getTargetSelection,
+ initImageDiff: utilsHelper.initImageDiff,
+};
diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js
new file mode 100644
index 00000000000..96fc735e629
--- /dev/null
+++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js
@@ -0,0 +1,95 @@
+import ImageBadge from '../image_badge';
+import ImageDiff from '../image_diff';
+import ReplacedImageDiff from '../replaced_image_diff';
+import '../../commit/image_file';
+
+export function resizeCoordinatesToImageElement(imageEl, meta) {
+ const { x, y, width, height } = meta;
+
+ const imageWidth = imageEl.width;
+ const imageHeight = imageEl.height;
+
+ const widthRatio = imageWidth / width;
+ const heightRatio = imageHeight / height;
+
+ return {
+ x: Math.round(x * widthRatio),
+ y: Math.round(y * heightRatio),
+ width: imageWidth,
+ height: imageHeight,
+ };
+}
+
+export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) {
+ const position = JSON.parse(discussionEl.dataset.position);
+ const firstNoteEl = discussionEl.querySelector('.note');
+ const badge = new ImageBadge({
+ actual: position,
+ imageEl: imageFrameEl.querySelector('img'),
+ noteId: firstNoteEl.id,
+ discussionId: discussionEl.dataset.discussionId,
+ });
+
+ return badge;
+}
+
+export function getTargetSelection(event) {
+ const containerEl = event.currentTarget;
+ const imageEl = containerEl.querySelector('img');
+
+ const x = event.offsetX;
+ const y = event.offsetY;
+
+ const width = imageEl.width;
+ const height = imageEl.height;
+
+ const actualWidth = imageEl.naturalWidth;
+ const actualHeight = imageEl.naturalHeight;
+
+ const widthRatio = actualWidth / width;
+ const heightRatio = actualHeight / height;
+
+ // Browser will include the frame as a clickable target,
+ // which would result in potential 1px out of bounds value
+ // This bound the coordinates to inside the frame
+ const normalizedX = Math.max(0, x) && Math.min(x, width);
+ const normalizedY = Math.max(0, y) && Math.min(y, height);
+
+ return {
+ browser: {
+ x: normalizedX,
+ y: normalizedY,
+ width,
+ height,
+ },
+ actual: {
+ // Round x, y so that we don't need to deal with decimals
+ x: Math.round(normalizedX * widthRatio),
+ y: Math.round(normalizedY * heightRatio),
+ width: actualWidth,
+ height: actualHeight,
+ },
+ };
+}
+
+export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) {
+ const options = {
+ canCreateNote,
+ renderCommentBadge,
+ };
+ let diff;
+
+ // ImageFile needs to be invoked before initImageDiff so that badges
+ // can mount to the correct location
+ new gl.ImageFile(fileEl); // eslint-disable-line no-new
+
+ if (fileEl.querySelector('.diff-file .js-single-image')) {
+ diff = new ImageDiff(fileEl, options);
+ diff.init();
+ } else if (fileEl.querySelector('.diff-file .js-replaced-image')) {
+ diff = new ReplacedImageDiff(fileEl, options);
+ diff.init();
+ }
+
+ return diff;
+}
diff --git a/app/assets/javascripts/image_diff/image_badge.js b/app/assets/javascripts/image_diff/image_badge.js
new file mode 100644
index 00000000000..51a8cda98d7
--- /dev/null
+++ b/app/assets/javascripts/image_diff/image_badge.js
@@ -0,0 +1,23 @@
+import imageDiffHelper from './helpers/index';
+
+const defaultMeta = {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+};
+
+export default class ImageBadge {
+ constructor(options) {
+ const { noteId, discussionId } = options;
+
+ this.actual = options.actual || defaultMeta;
+ this.browser = options.browser || defaultMeta;
+ this.noteId = noteId;
+ this.discussionId = discussionId;
+
+ if (options.imageEl && !options.browser) {
+ this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual);
+ }
+ }
+}
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
new file mode 100644
index 00000000000..f3af92cf2b0
--- /dev/null
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -0,0 +1,143 @@
+import imageDiffHelper from './helpers/index';
+import ImageBadge from './image_badge';
+import { isImageLoaded } from '../lib/utils/image_utility';
+
+export default class ImageDiff {
+ constructor(el, options) {
+ this.el = el;
+ this.canCreateNote = !!(options && options.canCreateNote);
+ this.renderCommentBadge = !!(options && options.renderCommentBadge);
+ this.$noteContainer = $('.note-container', this.el);
+ this.imageBadges = [];
+ }
+
+ init() {
+ this.imageFrameEl = this.el.querySelector('.diff-file .js-image-frame');
+ this.imageEl = this.imageFrameEl.querySelector('img');
+
+ this.bindEvents();
+ }
+
+ bindEvents() {
+ this.imageClickedWrapper = this.imageClicked.bind(this);
+ this.imageBlurredWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl);
+ this.addBadgeWrapper = this.addBadge.bind(this);
+ this.removeBadgeWrapper = this.removeBadge.bind(this);
+ this.renderBadgesWrapper = this.renderBadges.bind(this);
+
+ // Render badges
+ if (isImageLoaded(this.imageEl)) {
+ this.renderBadges();
+ } else {
+ this.imageEl.addEventListener('load', this.renderBadgesWrapper);
+ }
+
+ // jquery makes the event delegation here much simpler
+ this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed);
+ $(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick);
+
+ if (this.canCreateNote) {
+ this.el.addEventListener('click.imageDiff', this.imageClickedWrapper);
+ this.el.addEventListener('blur.imageDiff', this.imageBlurredWrapper);
+ this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper);
+ this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper);
+ }
+ }
+
+ imageClicked(event) {
+ const customEvent = event.detail;
+ const selection = imageDiffHelper.getTargetSelection(customEvent);
+ const el = customEvent.currentTarget;
+
+ imageDiffHelper.setPositionDataAttribute(el, selection.actual);
+ imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser);
+ }
+
+ renderBadges() {
+ const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes');
+ [...discussionsEls].forEach(this.renderBadge.bind(this));
+ }
+
+ renderBadge(discussionEl, index) {
+ const imageBadge = imageDiffHelper
+ .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
+
+ this.imageBadges.push(imageBadge);
+
+ const options = {
+ coordinate: imageBadge.browser,
+ noteId: imageBadge.noteId,
+ };
+
+ if (this.renderCommentBadge) {
+ imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
+ } else {
+ const numberBadgeOptions = Object.assign({}, options, {
+ badgeText: index + 1,
+ });
+
+ imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
+ }
+ }
+
+ addBadge(event) {
+ const { x, y, width, height, noteId, discussionId } = event.detail;
+ const badgeText = this.imageBadges.length + 1;
+ const imageBadge = new ImageBadge({
+ actual: {
+ x,
+ y,
+ width,
+ height,
+ },
+ imageEl: this.imageFrameEl.querySelector('img'),
+ noteId,
+ discussionId,
+ });
+
+ this.imageBadges.push(imageBadge);
+
+ imageDiffHelper.addImageBadge(this.imageFrameEl, {
+ coordinate: imageBadge.browser,
+ badgeText,
+ noteId,
+ });
+
+ imageDiffHelper.addAvatarBadge(this.el, {
+ detail: {
+ noteId,
+ badgeNumber: badgeText,
+ },
+ });
+
+ const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
+ imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText);
+ }
+
+ removeBadge(event) {
+ const { badgeNumber } = event.detail;
+ const indexToRemove = badgeNumber - 1;
+ const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge');
+
+ if (this.imageBadges.length !== badgeNumber) {
+ // Cascade badges count numbers for (avatar badges + image badges)
+ this.imageBadges.forEach((badge, index) => {
+ if (index > indexToRemove) {
+ const { discussionId } = badge;
+ const updatedBadgeNumber = index;
+ const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
+
+ imageBadgeEls[index].innerText = updatedBadgeNumber;
+
+ imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber);
+ imageDiffHelper.updateDiscussionAvatarBadgeNumber(discussionEl, updatedBadgeNumber);
+ }
+ });
+ }
+
+ this.imageBadges.splice(indexToRemove, 1);
+
+ const imageBadgeEl = imageBadgeEls[indexToRemove];
+ imageBadgeEl.remove();
+ }
+}
diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js
new file mode 100644
index 00000000000..2f16c6ef115
--- /dev/null
+++ b/app/assets/javascripts/image_diff/init_discussion_tab.js
@@ -0,0 +1,12 @@
+import imageDiffHelper from './helpers/index';
+
+export default () => {
+ // Always pass can-create-note as false because a user
+ // cannot place new badge markers on discussion tab
+ const canCreateNote = false;
+ const renderCommentBadge = true;
+
+ const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file');
+ [...diffFileEls].forEach(diffFileEl =>
+ imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge));
+};
diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js
new file mode 100644
index 00000000000..4abd13fb472
--- /dev/null
+++ b/app/assets/javascripts/image_diff/replaced_image_diff.js
@@ -0,0 +1,92 @@
+import imageDiffHelper from './helpers/index';
+import { viewTypes, isValidViewType } from './view_types';
+import ImageDiff from './image_diff';
+
+export default class ReplacedImageDiff extends ImageDiff {
+ init(defaultViewType = viewTypes.TWO_UP) {
+ this.imageFrameEls = {
+ [viewTypes.TWO_UP]: this.el.querySelector('.two-up .js-image-frame'),
+ [viewTypes.SWIPE]: this.el.querySelector('.swipe .js-image-frame'),
+ [viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .js-image-frame'),
+ };
+
+ const viewModesEl = this.el.querySelector('.view-modes-menu');
+ this.viewModesEls = {
+ [viewTypes.TWO_UP]: viewModesEl.querySelector('.two-up'),
+ [viewTypes.SWIPE]: viewModesEl.querySelector('.swipe'),
+ [viewTypes.ONION_SKIN]: viewModesEl.querySelector('.onion-skin'),
+ };
+
+ this.currentView = defaultViewType;
+ this.generateImageEls();
+ this.bindEvents();
+ }
+
+ generateImageEls() {
+ this.imageEls = {};
+
+ const viewTypeNames = Object.getOwnPropertyNames(viewTypes);
+ viewTypeNames.forEach((viewType) => {
+ this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img');
+ });
+ }
+
+ bindEvents() {
+ super.bindEvents();
+
+ this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP);
+ this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE);
+ this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN);
+
+ this.viewModesEls[viewTypes.TWO_UP].addEventListener('click', this.changeToViewTwoUp);
+ this.viewModesEls[viewTypes.SWIPE].addEventListener('click', this.changeToViewSwipe);
+ this.viewModesEls[viewTypes.ONION_SKIN].addEventListener('click', this.changeToViewOnionSkin);
+ }
+
+ get imageEl() {
+ return this.imageEls[this.currentView];
+ }
+
+ get imageFrameEl() {
+ return this.imageFrameEls[this.currentView];
+ }
+
+ changeView(newView) {
+ if (!isValidViewType(newView)) {
+ return;
+ }
+
+ const indicator = imageDiffHelper.removeCommentIndicator(this.imageFrameEl);
+
+ this.currentView = newView;
+
+ // Clear existing badges on new view
+ const existingBadges = this.imageFrameEl.querySelectorAll('.badge');
+ [...existingBadges].map(badge => badge.remove());
+
+ // Remove existing references to old view image badges
+ this.imageBadges = [];
+
+ // Image_file.js has a fade animation of 200ms for loading the view
+ // Need to wait an additional 250ms for the images to be displayed
+ // on window in order to re-normalize their dimensions
+ setTimeout(this.renderNewView.bind(this, indicator), 250);
+ }
+
+ renderNewView(indicator) {
+ // Generate badge coordinates on new view
+ this.renderBadges();
+
+ // Re-render indicator in new view
+ if (indicator.removed) {
+ const normalizedIndicator = imageDiffHelper
+ .resizeCoordinatesToImageElement(this.imageEl, {
+ x: indicator.x,
+ y: indicator.y,
+ width: indicator.image.width,
+ height: indicator.image.height,
+ });
+ imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
+ }
+ }
+}
diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js
new file mode 100644
index 00000000000..ab0a595571f
--- /dev/null
+++ b/app/assets/javascripts/image_diff/view_types.js
@@ -0,0 +1,9 @@
+export const viewTypes = {
+ TWO_UP: 'TWO_UP',
+ SWIPE: 'SWIPE',
+ ONION_SKIN: 'ONION_SKIN',
+};
+
+export function isValidViewType(validate) {
+ return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
+}
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 70c364e51fe..1d305f1eb2f 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -67,10 +67,13 @@ const PARTICIPANTS_ROW_COUNT = 7;
originalText = $(this).data("original-text");
if (currentText === originalText) {
$(this).text(lessText);
+
+ if (gl.lazyLoader) gl.lazyLoader.loadCheck();
} else {
$(this).text(originalText);
}
- return $(".js-participants-hidden").toggle();
+
+ $(".js-participants-hidden").toggle();
};
return IssuableContext;
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 2538d9c2093..d479f7ed682 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,6 +4,7 @@
import _ from 'underscore';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
+import CreateLabelDropdown from './create_label';
(function() {
this.LabelsSelect = (function() {
@@ -61,7 +62,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
$sidebarLabelTooltip.tooltip();
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
- new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath);
+ new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath);
}
saveLabelData = function() {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index ea2d61af9be..423a25fbdfa 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -71,6 +71,7 @@ export const handleLocationHash = () => {
// This is required to handle non-unicode characters in hash
hash = decodeURIComponent(hash);
+ const target = document.getElementById(hash) || document.getElementById(`user-content-${hash}`);
const fixedTabs = document.querySelector('.js-tabs-affix');
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
const fixedNav = document.querySelector('.navbar-gitlab');
@@ -78,25 +79,19 @@ export const handleLocationHash = () => {
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
- // scroll to user-generated markdown anchor if we cannot find a match
- if (document.getElementById(hash) === null) {
- const target = document.getElementById(`user-content-${hash}`);
- if (target && target.scrollIntoView) {
- target.scrollIntoView(true);
- window.scrollBy(0, adjustment);
- }
- } else {
- // only adjust for fixedTabs when not targeting user-generated content
- if (fixedTabs) {
- adjustment -= fixedTabs.offsetHeight;
- }
+ if (target && target.scrollIntoView) {
+ target.scrollIntoView(true);
+ }
- if (fixedDiffStats) {
- adjustment -= fixedDiffStats.offsetHeight;
- }
+ if (fixedTabs) {
+ adjustment -= fixedTabs.offsetHeight;
+ }
- window.scrollBy(0, adjustment);
+ if (fixedDiffStats) {
+ adjustment -= fixedDiffStats.offsetHeight;
}
+
+ window.scrollBy(0, adjustment);
};
// Check if element scrolled into viewport from above or below
diff --git a/app/assets/javascripts/lib/utils/csrf.js b/app/assets/javascripts/lib/utils/csrf.js
index ae41cc5e8a8..0bdb547d31a 100644
--- a/app/assets/javascripts/lib/utils/csrf.js
+++ b/app/assets/javascripts/lib/utils/csrf.js
@@ -14,6 +14,9 @@ If you need to compose a headers object, use the spread operator:
someOtherHeader: '12345',
}
```
+
+see also http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
+and https://github.com/rails/jquery-rails/blob/v4.3.1/vendor/assets/javascripts/jquery_ujs.js#L59-L62
*/
const csrf = {
@@ -53,4 +56,3 @@ if ($.rails) {
}
export default csrf;
-
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 1d1763c3963..29fc91733b3 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -55,7 +55,7 @@ window.dateFormat = dateFormat;
if (!timeagoInstance) {
const localeRemaining = function(number, index) {
return [
- [s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
+ [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
@@ -73,7 +73,7 @@ window.dateFormat = dateFormat;
};
locale = function(number, index) {
return [
- [s__('Timeago|less than a minute ago'), s__('Timeago|a while')],
+ [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
diff --git a/app/assets/javascripts/lib/utils/image_utility.js b/app/assets/javascripts/lib/utils/image_utility.js
new file mode 100644
index 00000000000..2977ec821cb
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/image_utility.js
@@ -0,0 +1,5 @@
+/* eslint-disable import/prefer-default-export */
+
+export function isImageLoaded(element) {
+ return element.complete && element.naturalHeight !== 0;
+}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 3328ff9cc23..78c7a094127 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
+
var base;
var w = window;
if (w.gl == null) {
@@ -86,6 +87,21 @@ w.gl.utils.getLocationHash = function(url) {
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
-w.gl.utils.visitUrl = (url) => {
- document.location.href = url;
+// eslint-disable-next-line import/prefer-default-export
+export function visitUrl(url, external = false) {
+ if (external) {
+ // Simulate `target="blank" rel="noopener noreferrer"`
+ // See https://mathiasbynens.github.io/rel-noopener/
+ const otherWindow = window.open();
+ otherWindow.opener = null;
+ otherWindow.location = url;
+ } else {
+ document.location.href = url;
+ }
+}
+
+window.gl = window.gl || {};
+window.gl.utils = {
+ ...(window.gl.utils || {}),
+ visitUrl,
};
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 7400c22543f..a75d1a4b8d0 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -28,148 +28,151 @@
// </div>
// </div>
//
-(function() {
- this.LineHighlighter = (function() {
- // CSS class applied to highlighted lines
- LineHighlighter.prototype.highlightClass = 'hll';
-
- // Internal copy of location.hash so we're not dependent on `location` in tests
- LineHighlighter.prototype._hash = '';
-
- function LineHighlighter(hash) {
- if (hash == null) {
- // Initialize a LineHighlighter object
- //
- // hash - String URL hash for dependency injection in tests
- hash = location.hash;
- }
- this.setHash = this.setHash.bind(this);
- this.highlightLine = this.highlightLine.bind(this);
- this.clickHandler = this.clickHandler.bind(this);
- this.highlightHash = this.highlightHash.bind(this);
- this._hash = hash;
- this.bindEvents();
- this.highlightHash();
- }
- LineHighlighter.prototype.bindEvents = function() {
- const $fileHolder = $('.file-holder');
- $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
- $fileHolder.on('highlight:line', this.highlightHash);
- };
-
- LineHighlighter.prototype.highlightHash = function() {
- var range;
- if (this._hash !== '') {
- range = this.hashToRange(this._hash);
- if (range[0]) {
- this.highlightRange(range);
- $.scrollTo("#L" + range[0], {
- // Scroll to the first highlighted line on initial load
- // Offset -50 for the sticky top bar, and another -100 for some context
- offset: -150
- });
- }
- }
- };
-
- LineHighlighter.prototype.clickHandler = function(event) {
- var current, lineNumber, range;
- event.preventDefault();
- this.clearHighlight();
- lineNumber = $(event.target).closest('a').data('line-number');
- current = this.hashToRange(this._hash);
- if (!(current[0] && event.shiftKey)) {
- // If there's no current selection, or there is but Shift wasn't held,
- // treat this like a single-line selection.
- this.setHash(lineNumber);
- return this.highlightLine(lineNumber);
- } else if (event.shiftKey) {
- if (lineNumber < current[0]) {
- range = [lineNumber, current[0]];
- } else {
- range = [current[0], lineNumber];
- }
- this.setHash(range[0], range[1]);
- return this.highlightRange(range);
- }
- };
-
- LineHighlighter.prototype.clearHighlight = function() {
- return $("." + this.highlightClass).removeClass(this.highlightClass);
- // Unhighlight previously highlighted lines
- };
-
- // Convert a URL hash String into line numbers
- //
- // hash - Hash String
- //
- // Examples:
- //
- // hashToRange('#L5') # => [5, null]
- // hashToRange('#L5-15') # => [5, 15]
- // hashToRange('#foo') # => [null, null]
- //
- // Returns an Array
- LineHighlighter.prototype.hashToRange = function(hash) {
- var first, last, matches;
- // ?L(\d+)(?:-(\d+))?$/)
- matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
- if (matches && matches.length) {
- first = parseInt(matches[1], 10);
- last = matches[2] ? parseInt(matches[2], 10) : null;
- return [first, last];
- } else {
- return [null, null];
- }
- };
-
- // Highlight a single line
- //
- // lineNumber - Line number to highlight
- LineHighlighter.prototype.highlightLine = function(lineNumber) {
- return $("#LC" + lineNumber).addClass(this.highlightClass);
- };
-
- // Highlight all lines within a range
- //
- // range - Array containing the starting and ending line numbers
- LineHighlighter.prototype.highlightRange = function(range) {
- var i, lineNumber, ref, ref1, results;
- if (range[1]) {
- results = [];
- for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) {
- results.push(this.highlightLine(lineNumber));
- }
- return results;
- } else {
- return this.highlightLine(range[0]);
- }
- };
+const LineHighlighter = function(options = {}) {
+ options.highlightLineClass = options.highlightLineClass || 'hll';
+ options.fileHolderSelector = options.fileHolderSelector || '.file-holder';
+ options.scrollFileHolder = options.scrollFileHolder || false;
+ options.hash = options.hash || location.hash;
- // Set the URL hash string
- LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
- var hash;
- if (lastLineNumber) {
- hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ this.options = options;
+ this._hash = options.hash;
+ this.highlightLineClass = options.highlightLineClass;
+ this.setHash = this.setHash.bind(this);
+ this.highlightLine = this.highlightLine.bind(this);
+ this.clickHandler = this.clickHandler.bind(this);
+ this.highlightHash = this.highlightHash.bind(this);
+
+ this.bindEvents();
+ this.highlightHash();
+};
+
+LineHighlighter.prototype.bindEvents = function() {
+ const $fileHolder = $(this.options.fileHolderSelector);
+
+ $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
+ $fileHolder.on('highlight:line', this.highlightHash);
+};
+
+LineHighlighter.prototype.highlightHash = function(newHash) {
+ let range;
+ if (newHash && typeof newHash === 'string') this._hash = newHash;
+
+ this.clearHighlight();
+
+ if (this._hash !== '') {
+ range = this.hashToRange(this._hash);
+ if (range[0]) {
+ this.highlightRange(range);
+ const lineSelector = `#L${range[0]}`;
+ const scrollOptions = {
+ // Scroll to the first highlighted line on initial load
+ // Offset -50 for the sticky top bar, and another -100 for some context
+ offset: -150
+ };
+ if (this.options.scrollFileHolder) {
+ $(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions);
} else {
- hash = "#L" + firstLineNumber;
+ $.scrollTo(lineSelector, scrollOptions);
}
- this._hash = hash;
- return this.__setLocationHash__(hash);
- };
-
- // Make the actual hash change in the browser
- //
- // This method is stubbed in tests.
- LineHighlighter.prototype.__setLocationHash__ = function(value) {
- return history.pushState({
- url: value
- // We're using pushState instead of assigning location.hash directly to
- // prevent the page from scrolling on the hashchange event
- }, document.title, value);
- };
-
- return LineHighlighter;
- })();
-}).call(window);
+ }
+ }
+};
+
+LineHighlighter.prototype.clickHandler = function(event) {
+ var current, lineNumber, range;
+ event.preventDefault();
+ this.clearHighlight();
+ lineNumber = $(event.target).closest('a').data('line-number');
+ current = this.hashToRange(this._hash);
+ if (!(current[0] && event.shiftKey)) {
+ // If there's no current selection, or there is but Shift wasn't held,
+ // treat this like a single-line selection.
+ this.setHash(lineNumber);
+ return this.highlightLine(lineNumber);
+ } else if (event.shiftKey) {
+ if (lineNumber < current[0]) {
+ range = [lineNumber, current[0]];
+ } else {
+ range = [current[0], lineNumber];
+ }
+ this.setHash(range[0], range[1]);
+ return this.highlightRange(range);
+ }
+};
+
+LineHighlighter.prototype.clearHighlight = function() {
+ return $("." + this.highlightLineClass).removeClass(this.highlightLineClass);
+};
+
+// Convert a URL hash String into line numbers
+//
+// hash - Hash String
+//
+// Examples:
+//
+// hashToRange('#L5') # => [5, null]
+// hashToRange('#L5-15') # => [5, 15]
+// hashToRange('#foo') # => [null, null]
+//
+// Returns an Array
+LineHighlighter.prototype.hashToRange = function(hash) {
+ var first, last, matches;
+ // ?L(\d+)(?:-(\d+))?$/)
+ matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
+ if (matches && matches.length) {
+ first = parseInt(matches[1], 10);
+ last = matches[2] ? parseInt(matches[2], 10) : null;
+ return [first, last];
+ } else {
+ return [null, null];
+ }
+};
+
+// Highlight a single line
+//
+// lineNumber - Line number to highlight
+LineHighlighter.prototype.highlightLine = function(lineNumber) {
+ return $("#LC" + lineNumber).addClass(this.highlightLineClass);
+};
+
+// Highlight all lines within a range
+//
+// range - Array containing the starting and ending line numbers
+LineHighlighter.prototype.highlightRange = function(range) {
+ var i, lineNumber, ref, ref1, results;
+ if (range[1]) {
+ results = [];
+ for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) {
+ results.push(this.highlightLine(lineNumber));
+ }
+ return results;
+ } else {
+ return this.highlightLine(range[0]);
+ }
+};
+
+// Set the URL hash string
+LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
+ var hash;
+ if (lastLineNumber) {
+ hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ } else {
+ hash = "#L" + firstLineNumber;
+ }
+ this._hash = hash;
+ return this.__setLocationHash__(hash);
+};
+
+// Make the actual hash change in the browser
+//
+// This method is stubbed in tests.
+LineHighlighter.prototype.__setLocationHash__ = function(value) {
+ return history.pushState({
+ url: value
+ // We're using pushState instead of assigning location.hash directly to
+ // prevent the page from scrolling on the hashchange event
+ }, document.title, value);
+};
+
+window.LineHighlighter = LineHighlighter;
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index 7ba676d6d20..1003b9ba0af 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -1,29 +1,13 @@
import Jed from 'jed';
+import sprintf from './sprintf';
-/**
- This is required to require all the translation folders in the current directory
- this saves us having to do this manually & keep up to date with new languages
-**/
-function requireAll(requireContext) { return requireContext.keys().map(requireContext); }
-
-const allLocales = requireAll(require.context('./', true, /^(?!.*(?:index.js$)).*\.js$/));
-const locales = allLocales.reduce((d, obj) => {
- const data = d;
- const localeKey = Object.keys(obj)[0];
-
- data[localeKey] = obj[localeKey];
-
- return data;
-}, {});
-
-let lang = document.querySelector('html').getAttribute('lang') || 'en';
-lang = lang.replace(/-/g, '_');
-
-const locale = new Jed(locales[lang]);
+const langAttribute = document.querySelector('html').getAttribute('lang');
+const lang = (langAttribute || 'en').replace(/-/g, '_');
+const locale = new Jed(window.translations || {});
+delete window.translations;
/**
Translates `text`
-
@param text The text to be translated
@returns {String} The translated text
**/
@@ -67,4 +51,5 @@ export { lang };
export { gettext as __ };
export { ngettext as n__ };
export { pgettext as s__ };
+export { sprintf };
export default locale;
diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js
new file mode 100644
index 00000000000..5f4a053f98e
--- /dev/null
+++ b/app/assets/javascripts/locale/sprintf.js
@@ -0,0 +1,26 @@
+import _ from 'underscore';
+
+/**
+ Very limited implementation of sprintf supporting only named parameters.
+
+ @param input (translated) text with parameters (e.g. '%{num_users} users use us')
+ @param parameters object mapping parameter names to values (e.g. { num_users: 5 })
+ @param escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
+ @returns {String} the text with parameters replaces (e.g. '5 users use us')
+
+ @see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
+ @see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992
+**/
+export default (input, parameters, escapeParameters = true) => {
+ let output = input;
+
+ if (parameters) {
+ Object.keys(parameters).forEach((parameterName) => {
+ const parameterValue = parameters[parameterName];
+ const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
+ output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
+ });
+ }
+
+ return output;
+};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ec001b9b31c..64d7c80e689 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -35,12 +35,9 @@ import './shortcuts_network';
import './templates/issuable_template_selector';
import './templates/issuable_template_selectors';
-// commit
-import './commit/file';
import './commit/image_file';
// lib/utils
-import './lib/utils/bootstrap_linked_tabs';
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import './lib/utils/pretty_time';
@@ -60,7 +57,6 @@ import './u2f/util';
import './abuse_reports';
import './activities';
import './admin';
-import './ajax_loading_spinner';
import './api';
import './aside';
import './autosave';
@@ -71,14 +67,12 @@ import './build';
import './build_artifacts';
import './build_variables';
import './ci_lint_editor';
-import './commit';
import './commits';
import './compare';
import './compare_autocomplete';
import './confirm_danger_modal';
import './copy_as_gfm';
import './copy_to_clipboard';
-import './create_label';
import './diff';
import './dropzone_input';
import './due_date_select';
@@ -111,7 +105,6 @@ import './merge_request';
import './merge_request_tabs';
import './milestone';
import './milestone_select';
-import './mini_pipeline_graph_dropdown';
import './namespace_select';
import './new_branch_form';
import './new_commit_form';
@@ -119,12 +112,10 @@ import './notes';
import './notifications_dropdown';
import './notifications_form';
import './pager';
-import './pipelines';
import './preview_markdown';
import './project';
import './project_avatar';
import './project_find_file';
-import './project_fork';
import './project_import';
import './project_label_subscription';
import './project_new';
@@ -302,7 +293,10 @@ $(function () {
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded'));
+ $('.navbar-toggle').on('click', () => {
+ $('.header-content').toggleClass('menu-expanded');
+ gl.lazyLoader.loadCheck();
+ });
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 0db2abe507d..af0658eb668 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -127,6 +127,21 @@ import IssuablesHelper from './helpers/issuables_helper';
$el.text(gl.text.addDelimiter(count));
};
+ MergeRequest.prototype.hideCloseButton = function() {
+ const el = document.querySelector('.merge-request .issuable-actions');
+ const closeDropdownItem = el.querySelector('li.close-item');
+ if (closeDropdownItem) {
+ closeDropdownItem.classList.add('hidden');
+ // Selects the next dropdown item
+ el.querySelector('li.report-item').click();
+ } else {
+ // No dropdown just hide the Close button
+ el.querySelector('.btn-close').classList.add('hidden');
+ }
+ // Dropdown for mobile screen
+ el.querySelector('li.js-close-item').classList.add('hidden');
+ };
+
return MergeRequest;
})();
}).call(window);
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 8ae127776e8..c042b22d1fd 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -13,6 +13,8 @@ import {
isMetaClick,
} from './lib/utils/common_utils';
+import initDiscussionTab from './image_diff/init_discussion_tab';
+
/* eslint-disable max-len */
// MergeRequestTabs
//
@@ -154,6 +156,8 @@ import {
}
this.resetViewContainer();
this.destroyPipelinesView();
+
+ initDiscussionTab();
}
if (this.setUrl) {
this.setCurrentAction(action);
@@ -352,7 +356,7 @@ import {
}
expandViewContainer() {
- const $wrapper = $('.content-wrapper .container-fluid');
+ const $wrapper = $('.content-wrapper .container-fluid').not('.breadcrumbs');
if (this.fixedLayoutPref === null) {
this.fixedLayoutPref = $wrapper.hasClass('container-limited');
}
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index f80a26b3fd4..442ed86d50c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -29,6 +29,7 @@
showEmptyState: true,
updateAspectRatio: false,
updatedAspectRatios: 0,
+ hoverData: {},
resizeThrottled: {},
};
},
@@ -64,6 +65,10 @@
this.updatedAspectRatios = 0;
}
},
+
+ hoverChanged(data) {
+ this.hoverData = data;
+ },
},
created() {
@@ -72,10 +77,12 @@
deploymentEndpoint: this.deploymentEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
+ eventHub.$on('hoverChanged', this.hoverChanged);
},
beforeDestroy() {
eventHub.$off('toggleAspectRatio', this.toggleAspectRatio);
+ eventHub.$off('hoverChanged', this.hoverChanged);
window.removeEventListener('resize', this.resizeThrottled, false);
},
@@ -102,6 +109,7 @@
v-for="(graphData, index) in groupData.metrics"
:key="index"
:graph-data="graphData"
+ :hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
/>
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index a7b483f6786..a18164482a2 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -73,34 +73,22 @@
<template>
<div class="prometheus-state">
- <div class="row">
- <div class="col-md-4 col-md-offset-4 state-svg svg-content">
- <img :src="currentState.svgUrl"/>
- </div>
+ <div class="state-svg svg-content">
+ <img :src="currentState.svgUrl"/>
</div>
- <div class="row">
- <div class="col-md-6 col-md-offset-3">
- <h4 class="text-center state-title">
- {{currentState.title}}
- </h4>
- </div>
- </div>
- <div class="row">
- <div class="col-md-6 col-md-offset-3">
- <div class="description-text text-center state-description">
- {{currentState.description}}
- <a v-if="showButtonDescription" :href="settingsPath">
- Prometheus server
- </a>
- </div>
- </div>
- </div>
- <div class="row state-button-section">
- <div class="col-md-4 col-md-offset-4 text-center state-button">
- <a class="btn btn-success" :href="buttonPath">
- {{currentState.buttonText}}
- </a>
- </div>
+ <h4 class="state-title">
+ {{currentState.title}}
+ </h4>
+ <p class="state-description">
+ {{currentState.description}}
+ <a v-if="showButtonDescription" :href="settingsPath">
+ Prometheus server
+ </a>
+ </p>
+ <div class="state-button">
+ <a class="btn btn-success" :href="buttonPath">
+ {{currentState.buttonText}}
+ </a>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 6b3e341f936..5aa3865f96a 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -3,16 +3,14 @@
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
- import GraphPath from './graph_path.vue';
+ import GraphPath from './graph/path.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
- import { timeScaleFormat } from '../utils/date_time_formatters';
+ import { timeScaleFormat, bisectDate } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints';
- const bisectDate = d3.bisector(d => d.time).left;
-
export default {
props: {
graphData: {
@@ -27,6 +25,11 @@
type: Array,
required: true,
},
+ hoverData: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
mixins: [MonitoringMixin],
@@ -52,6 +55,7 @@
currentXCoordinate: 0,
currentFlagPosition: 0,
showFlag: false,
+ showFlagContent: false,
showDeployInfo: true,
timeSeries: [],
};
@@ -65,7 +69,7 @@
},
computed: {
- outterViewBox() {
+ outerViewBox() {
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
},
@@ -122,36 +126,30 @@
const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
- this.currentData = evalTime ? d1 : d0;
- this.currentDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
- this.currentXCoordinate = Math.floor(firstTimeSeries.timeSeriesScaleX(this.currentData.time));
+ const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
+ const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
- if (this.currentXCoordinate > (this.graphWidth - 200)) {
- this.currentFlagPosition = this.currentXCoordinate - 103;
- } else {
- this.currentFlagPosition = this.currentXCoordinate;
- }
-
- if (currentDeployXPos) {
- this.showFlag = false;
- } else {
- this.showFlag = true;
- }
+ eventHub.$emit('hoverChanged', {
+ hoveredDate,
+ currentDeployXPos,
+ });
},
renderAxesPaths() {
- this.timeSeries = createTimeSeries(this.graphData.queries[0],
- this.graphWidth,
- this.graphHeight,
- this.graphHeightOffset);
+ this.timeSeries = createTimeSeries(
+ this.graphData.queries[0],
+ this.graphWidth,
+ this.graphHeight,
+ this.graphHeightOffset,
+ );
if (this.timeSeries.length > 3) {
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
}
const axisXScale = d3.time.scale()
- .range([0, this.graphWidth]);
+ .range([0, this.graphWidth - 70]);
const axisYScale = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
@@ -194,6 +192,10 @@
eventHub.$emit('toggleAspectRatio');
}
},
+
+ hoverData() {
+ this.positionFlag();
+ },
},
mounted() {
@@ -203,7 +205,10 @@
</script>
<template>
- <div class="prometheus-graph">
+ <div
+ class="prometheus-graph"
+ @mouseover="showFlagContent = true"
+ @mouseleave="showFlagContent = false">
<h5 class="text-center graph-title">
{{graphData.title}}
</h5>
@@ -211,7 +216,7 @@
class="prometheus-svg-container"
:style="paddingBottomRootSvg">
<svg
- :viewBox="outterViewBox"
+ :viewBox="outerViewBox"
ref="baseSvg">
<g
class="x-axis"
@@ -247,6 +252,7 @@
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
+ :graph-width="graphWidth"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
@@ -257,6 +263,7 @@
:current-flag-position="currentFlagPosition"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
+ :show-flag-content="showFlagContent"
/>
<rect
class="prometheus-graph-overlay"
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index 3623d2ed946..e3b8be0c7fb 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -19,6 +19,10 @@
type: Number,
required: true,
},
+ graphWidth: {
+ type: Number,
+ required: true,
+ },
},
computed: {
@@ -47,6 +51,14 @@
transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
},
+
+ positionFlag(deployment) {
+ let xPosition = 3;
+ if (deployment.xPos > (this.graphWidth - 200)) {
+ xPosition = -97;
+ }
+ return xPosition;
+ },
},
};
</script>
@@ -77,7 +89,7 @@
<svg
v-if="deployment.showDeploymentFlag"
class="js-deploy-info-box"
- x="3"
+ :x="positionFlag(deployment)"
y="0"
width="92"
height="60">
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index a98e3d06c18..10fb7ff6803 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -23,6 +23,10 @@
type: Number,
required: true,
},
+ showFlagContent: {
+ type: Boolean,
+ required: true,
+ },
},
data() {
@@ -57,6 +61,7 @@
transform="translate(-5, 20)">
</line>
<svg
+ v-if="showFlagContent"
class="rect-text-metric"
:x="currentFlagPosition"
y="0">
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index dbc48c63747..85b6d7f4cbe 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -79,7 +79,11 @@
},
formatMetricUsage(series) {
- return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`;
+ const value = series.values[this.currentDataIndex].value;
+ if (isNaN(value)) {
+ return '-';
+ }
+ return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
},
createSeriesString(index, series) {
diff --git a/app/assets/javascripts/monitoring/components/graph_path.vue b/app/assets/javascripts/monitoring/components/graph/path.vue
index 043f1bf66bb..043f1bf66bb 100644
--- a/app/assets/javascripts/monitoring/components/graph_path.vue
+++ b/app/assets/javascripts/monitoring/components/graph/path.vue
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 345a0b37a76..31f38aca5d6 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -1,3 +1,5 @@
+import { bisectDate } from '../utils/date_time_formatters';
+
const mixins = {
methods: {
mouseOverDeployInfo(mouseXPos) {
@@ -18,6 +20,7 @@ const mixins = {
return dataFound;
},
+
formatDeployments() {
this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
@@ -40,6 +43,25 @@ const mixins = {
return deploymentDataArray;
}, []);
},
+
+ positionFlag() {
+ const timeSeries = this.timeSeries[0];
+ const hoveredDataIndex = bisectDate(timeSeries.values, this.hoverData.hoveredDate, 1);
+ this.currentData = timeSeries.values[hoveredDataIndex];
+ this.currentDataIndex = hoveredDataIndex;
+ this.currentXCoordinate = Math.floor(timeSeries.timeSeriesScaleX(this.currentData.time));
+ if (this.currentXCoordinate > (this.graphWidth - 200)) {
+ this.currentFlagPosition = this.currentXCoordinate - 103;
+ } else {
+ this.currentFlagPosition = this.currentXCoordinate;
+ }
+
+ if (this.hoverData.currentDeployXPos) {
+ this.showFlag = false;
+ } else {
+ this.showFlag = true;
+ }
+ },
},
};
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index ef280e02092..104432ef5de 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -3,8 +3,5 @@ import Dashboard from './components/dashboard.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#prometheus-graphs',
- components: {
- Dashboard,
- },
- render: createElement => createElement('dashboard'),
+ render: createElement => createElement(Dashboard),
}));
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js
index 7592af5878e..854636e9a89 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js
@@ -13,7 +13,7 @@ function normalizeMetrics(metrics) {
...result,
values: result.values.map(([timestamp, value]) => ({
time: new Date(timestamp * 1000),
- value,
+ value: Number(value),
})),
})),
})),
diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
index 26bcaa02511..c4c6b1ac1f5 100644
--- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js
+++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
@@ -2,6 +2,7 @@ import d3 from 'd3';
export const dateFormat = d3.time.format('%b %-d, %Y');
export const timeFormat = d3.time.format('%-I:%M%p');
+export const bisectDate = d3.bisector(d => d.time).left;
export const timeScaleFormat = d3.time.format.multi([
['.%L', d => d.getMilliseconds()],
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index 3cbe06d8fd6..65eec0d8d02 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -56,12 +56,16 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
timeSeriesScaleX.ticks(d3.time.minute, 60);
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
+ const defined = d => !isNaN(d.value) && d.value != null;
+
const lineFunction = d3.svg.line()
+ .defined(defined)
.interpolate('linear')
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area()
+ .defined(defined)
.interpolate('linear')
.x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset)
diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue
index b8a16356576..b4067d229aa 100644
--- a/app/assets/javascripts/notebook/cells/code.vue
+++ b/app/assets/javascripts/notebook/cells/code.vue
@@ -1,18 +1,3 @@
-<template>
- <div class="cell">
- <code-cell
- type="input"
- :raw-code="rawInputCode"
- :count="cell.execution_count"
- :code-css-class="codeCssClass" />
- <output-cell
- v-if="hasOutput"
- :count="cell.execution_count"
- :output="output"
- :code-css-class="codeCssClass" />
- </div>
-</template>
-
<script>
import CodeCell from './code/index.vue';
import OutputCell from './output/index.vue';
@@ -51,6 +36,21 @@ export default {
};
</script>
+<template>
+ <div class="cell">
+ <code-cell
+ type="input"
+ :raw-code="rawInputCode"
+ :count="cell.execution_count"
+ :code-css-class="codeCssClass" />
+ <output-cell
+ v-if="hasOutput"
+ :count="cell.execution_count"
+ :output="output"
+ :code-css-class="codeCssClass" />
+ </div>
+</template>
+
<style scoped>
.cell {
flex-direction: column;
diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue
index 31b30f601e2..0f3083f05b2 100644
--- a/app/assets/javascripts/notebook/cells/code/index.vue
+++ b/app/assets/javascripts/notebook/cells/code/index.vue
@@ -1,17 +1,3 @@
-<template>
- <div :class="type">
- <prompt
- :type="promptType"
- :count="count" />
- <pre
- class="language-python"
- :class="codeCssClass"
- ref="code"
- v-text="code">
- </pre>
- </div>
-</template>
-
<script>
import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue';
@@ -55,3 +41,17 @@
},
};
</script>
+
+<template>
+ <div :class="type">
+ <prompt
+ :type="promptType"
+ :count="count" />
+ <pre
+ class="language-python"
+ :class="codeCssClass"
+ ref="code"
+ v-text="code">
+ </pre>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 814d2ea92b4..82c51a1068c 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -1,10 +1,3 @@
-<template>
- <div class="cell text-cell">
- <prompt />
- <div class="markdown" v-html="markdown"></div>
- </div>
-</template>
-
<script>
/* global katex */
import marked from 'marked';
@@ -95,6 +88,13 @@
};
</script>
+<template>
+ <div class="cell text-cell">
+ <prompt />
+ <div class="markdown" v-html="markdown"></div>
+ </div>
+</template>
+
<style>
.markdown .katex {
display: block;
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index 0f39cd138df..2110a9de7ed 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,10 +1,3 @@
-<template>
- <div class="output">
- <prompt />
- <div v-html="rawCode"></div>
- </div>
-</template>
-
<script>
import Prompt from '../prompt.vue';
@@ -20,3 +13,10 @@ export default {
},
};
</script>
+
+<template>
+ <div class="output">
+ <prompt />
+ <div v-html="rawCode"></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index f3b873bbc0f..fbb39ea6e2d 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -1,11 +1,3 @@
-<template>
- <div class="output">
- <prompt />
- <img
- :src="'data:' + outputType + ';base64,' + rawCode" />
- </div>
-</template>
-
<script>
import Prompt from '../prompt.vue';
@@ -25,3 +17,11 @@ export default {
},
};
</script>
+
+<template>
+ <div class="output">
+ <prompt />
+ <img
+ :src="'data:' + outputType + ';base64,' + rawCode" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index 23c9ea78939..05af0bf1e8e 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -1,12 +1,3 @@
-<template>
- <component :is="componentName"
- type="output"
- :outputType="outputType"
- :count="count"
- :raw-code="rawCode"
- :code-css-class="codeCssClass" />
-</template>
-
<script>
import CodeCell from '../code/index.vue';
import Html from './html.vue';
@@ -81,3 +72,12 @@ export default {
},
};
</script>
+
+<template>
+ <component :is="componentName"
+ type="output"
+ :outputType="outputType"
+ :count="count"
+ :raw-code="rawCode"
+ :code-css-class="codeCssClass" />
+</template>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
index 4540e4248d8..039fb99293d 100644
--- a/app/assets/javascripts/notebook/cells/prompt.vue
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -1,11 +1,3 @@
-<template>
- <div class="prompt">
- <span v-if="type && count">
- {{ type }} [{{ count }}]:
- </span>
- </div>
-</template>
-
<script>
export default {
props: {
@@ -21,6 +13,14 @@
};
</script>
+<template>
+ <div class="prompt">
+ <span v-if="type && count">
+ {{ type }} [{{ count }}]:
+ </span>
+ </div>
+</template>
+
<style scoped>
.prompt {
padding: 0 10px;
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index fd62c1231ef..e88806431af 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -1,14 +1,3 @@
-<template>
- <div v-if="hasNotebook">
- <component
- v-for="(cell, index) in cells"
- :is="cellType(cell.cell_type)"
- :cell="cell"
- :key="index"
- :code-css-class="codeCssClass" />
- </div>
-</template>
-
<script>
import {
MarkdownCell,
@@ -59,6 +48,17 @@
};
</script>
+<template>
+ <div v-if="hasNotebook">
+ <component
+ v-for="(cell, index) in cells"
+ :is="cellType(cell.cell_type)"
+ :cell="cell"
+ :key="index"
+ :code-css-class="codeCssClass" />
+ </div>
+</template>
+
<style>
.cell,
.input,
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 93aa29454a0..24de21f2ce2 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -24,6 +24,7 @@ import './autosave';
import './dropzone_input';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
+import imageDiffHelper from './image_diff/helpers/index';
window.autosize = autosize;
window.Dropzone = Dropzone;
@@ -42,6 +43,7 @@ export default class Notes {
this.visibilityChange = this.visibilityChange.bind(this);
this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
this.onAddDiffNote = this.onAddDiffNote.bind(this);
+ this.onAddImageDiffNote = this.onAddImageDiffNote.bind(this);
this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
this.removeNote = this.removeNote.bind(this);
@@ -114,6 +116,8 @@ export default class Notes {
$(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
// add diff note
$(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
+ // add diff note for images
+ $(document).on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
// hide diff note form
$(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
// toggle commit list
@@ -140,6 +144,7 @@ export default class Notes {
$(document).off('click', '.js-note-attachment-delete');
$(document).off('click', '.js-discussion-reply-button');
$(document).off('click', '.js-add-diff-note-button');
+ $(document).off('click', '.js-add-image-diff-note-button');
$(document).off('visibilitychange');
$(document).off('keyup input', '.js-note-text');
$(document).off('click', '.js-note-target-reopen');
@@ -412,6 +417,11 @@ export default class Notes {
this.note_ids.push(noteEntity.id);
form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
row = form.closest('tr');
+
+ if (noteEntity.on_image) {
+ row = form;
+ }
+
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion?
@@ -423,7 +433,7 @@ export default class Notes {
if (noteEntity.diff_discussion_html) {
var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
- if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) {
+ if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
// insert the note and the reply button after the temp row
row.after($discussion);
} else {
@@ -449,6 +459,7 @@ export default class Notes {
if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
gl.diffNotesCompileComponents();
+
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
@@ -561,7 +572,7 @@ export default class Notes {
form.find('#note_line_code').val(),
// DiffNote
- form.find('#note_position').val()
+ form.find('#note_position').val(),
];
return new Autosave(textarea, key);
}
@@ -783,9 +794,22 @@ export default class Notes {
$(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
// The notes tr can contain multiple lists of notes, like on the parallel diff
- if (notesTr.find('.discussion-notes').length > 1) {
+ // notesTr does not exist for image diffs
+ if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
+ const $diffFile = $notes.closest('.diff-file');
+ if ($diffFile.length > 0) {
+ const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
+ detail: {
+ // badgeNumber's start with 1 and index starts with 0
+ badgeNumber: $notes.index() + 1,
+ },
+ });
+
+ $diffFile[0].dispatchEvent(removeBadgeEvent);
+ }
+
$notes.remove();
- } else {
+ } else if (notesTr.length > 0) {
notesTr.remove();
}
}
@@ -841,7 +865,11 @@ export default class Notes {
*/
setupDiscussionNoteForm(dataHolder, form) {
// setup note target
- const diffFileData = dataHolder.closest('.text-file');
+ let diffFileData = dataHolder.closest('.text-file');
+
+ if (diffFileData.length === 0) {
+ diffFileData = dataHolder.closest('.image');
+ }
var discussionID = dataHolder.data('discussionId');
@@ -907,6 +935,31 @@ export default class Notes {
});
}
+ onAddImageDiffNote(e) {
+ const $link = $(e.currentTarget || e.target);
+ const $diffFile = $link.closest('.diff-file');
+
+ const clickEvent = new CustomEvent('click.imageDiff', {
+ detail: e,
+ });
+
+ $diffFile[0].dispatchEvent(clickEvent);
+
+ // Setup comment form
+ let newForm;
+ const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
+ const $form = $noteContainer.find('> .discussion-form');
+
+ if ($form.length === 0) {
+ newForm = this.cleanForm(this.formClone.clone());
+ newForm.appendTo($noteContainer);
+ } else {
+ newForm = $form;
+ }
+
+ this.setupDiscussionNoteForm($link, newForm);
+ }
+
toggleDiffNote({
target,
lineType,
@@ -999,10 +1052,25 @@ export default class Notes {
}
cancelDiscussionForm(e) {
- var form;
e.preventDefault();
- form = $(e.target).closest('.js-discussion-note-form');
- return this.removeDiscussionNoteForm(form);
+ const $form = $(e.target).closest('.js-discussion-note-form');
+ const $discussionNote = $(e.target).closest('.discussion-notes');
+
+ if ($discussionNote.length === 0) {
+ // Only send blur event when the discussion form
+ // is not part of a discussion note
+ const $diffFile = $form.closest('.diff-file');
+
+ if ($diffFile.length > 0) {
+ const blurEvent = new CustomEvent('blur.imageDiff', {
+ detail: e,
+ });
+
+ $diffFile[0].dispatchEvent(blurEvent);
+ }
+ }
+
+ return this.removeDiscussionNoteForm($form);
}
/**
@@ -1414,6 +1482,15 @@ export default class Notes {
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
+ const $diffFile = $form.closest('.diff-file');
+ if ($diffFile.length > 0) {
+ const blurEvent = new CustomEvent('blur.imageDiff', {
+ detail: e,
+ });
+
+ $diffFile[0].dispatchEvent(blurEvent);
+ }
+
// Reset cached commands list when command is applied
if (hasQuickActions) {
$form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
@@ -1436,7 +1513,28 @@ export default class Notes {
}
// Show final note element on UI
- this.addDiscussionNote($form, note, $notesContainer.length === 0);
+ const isNewDiffComment = $notesContainer.length === 0;
+ this.addDiscussionNote($form, note, isNewDiffComment);
+
+ if (isNewDiffComment) {
+ // Add image badge, avatar badge and toggle discussion badge for new image diffs
+ const notePosition = $form.find('#note_position').val();
+ if ($diffFile.length > 0 && notePosition.length > 0) {
+ const { x, y, width, height } = JSON.parse(notePosition);
+ const addBadgeEvent = new CustomEvent('addBadge.imageDiff', {
+ detail: {
+ x,
+ y,
+ width,
+ height,
+ noteId: `note_${note.id}`,
+ discussionId: note.discussion_id,
+ },
+ });
+
+ $diffFile[0].dispatchEvent(addBadgeEvent);
+ }
+ }
// append flash-container to the Notes list
if ($notesContainer.length) {
@@ -1457,6 +1555,16 @@ export default class Notes {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
+ const blurEvent = new CustomEvent('blur.imageDiff', {
+ detail: e,
+ });
+
+ const closestDiffFile = $form.closest('.diff-file');
+
+ if (closestDiffFile.length) {
+ closestDiffFile[0].dispatchEvent(blurEvent);
+ }
+
if (hasQuickActions) {
$notesContainer.find(`#${systemNoteUniqueId}`).remove();
}
@@ -1500,6 +1608,8 @@ export default class Notes {
const $noteBody = $editingNote.find('.js-task-list-container');
const $noteBodyText = $noteBody.find('.note-text');
const { formData, formContent, formAction } = this.getFormData($form);
+ const $diffFile = $form.closest('.diff-file');
+ const $notesContainer = $form.closest('.notes');
// Cache original comment content
const cachedNoteBodyText = $noteBodyText.html();
diff --git a/app/assets/javascripts/notes/components/issue_comment_form.vue b/app/assets/javascripts/notes/components/issue_comment_form.vue
index fa7ac994058..ab8516296a8 100644
--- a/app/assets/javascripts/notes/components/issue_comment_form.vue
+++ b/app/assets/javascripts/notes/components/issue_comment_form.vue
@@ -7,10 +7,12 @@
import TaskList from '../../task_list';
import * as constants from '../constants';
import eventHub from '../event_hub';
- import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue';
+ import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
+ import issueDiscussionLockedWidget from './issue_discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+ import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'issueCommentForm',
@@ -26,8 +28,9 @@
};
},
components: {
- confidentialIssue,
+ issueWarning,
issueNoteSignedOutWidget,
+ issueDiscussionLockedWidget,
markdownField,
userAvatarLink,
},
@@ -55,6 +58,9 @@
isIssueOpen() {
return this.issueState === constants.OPENED || this.issueState === constants.REOPENED;
},
+ canCreateNote() {
+ return this.getIssueData.current_user.can_create_note;
+ },
issueActionButtonTitle() {
if (this.note.length) {
const actionText = this.isIssueOpen ? 'close' : 'reopen';
@@ -90,9 +96,6 @@
endpoint() {
return this.getIssueData.create_note_path;
},
- isConfidentialIssue() {
- return this.getIssueData.confidential;
- },
},
methods: {
...mapActions([
@@ -220,6 +223,9 @@
});
},
},
+ mixins: [
+ issuableStateMixin,
+ ],
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
@@ -235,6 +241,7 @@
<template>
<div>
<issue-note-signed-out-widget v-if="!isLoggedIn" />
+ <issue-discussion-locked-widget v-else-if="!canCreateNote" />
<ul
v-else
class="notes notes-form timeline">
@@ -253,15 +260,22 @@
<div class="timeline-content timeline-content-form">
<form
ref="commentForm"
- class="new-note js-quick-submit common-note-form gfm-form js-main-target-form">
- <confidentialIssue v-if="isConfidentialIssue" />
+ class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
+ >
+
<div class="error-alert"></div>
+
+ <issue-warning
+ v-if="hasWarning(getIssueData)"
+ :is-locked="isLocked(getIssueData)"
+ :is-confidential="isConfidential(getIssueData)"
+ />
+
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false"
- :is-confidential-issue="isConfidentialIssue"
ref="markdownField">
<textarea
id="note-body"
@@ -272,6 +286,7 @@
v-model="note"
ref="textarea"
slot="textarea"
+ :disabled="isSubmitting"
placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()">
diff --git a/app/assets/javascripts/notes/components/issue_discussion_locked_widget.vue b/app/assets/javascripts/notes/components/issue_discussion_locked_widget.vue
new file mode 100644
index 00000000000..e73ec2aaf71
--- /dev/null
+++ b/app/assets/javascripts/notes/components/issue_discussion_locked_widget.vue
@@ -0,0 +1,19 @@
+<script>
+ export default {
+ computed: {
+ lockIcon() {
+ return gl.utils.spriteIcon('lock');
+ },
+ },
+ };
+
+</script>
+
+<template>
+ <div class="disabled-comment text-center">
+ <span class="issuable-note-warning">
+ <span class="icon" v-html="lockIcon"></span>
+ <span>This issue is locked. Only <b>project members</b> can comment.</span>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue
index 3483f6c7538..1f43b8a16ad 100644
--- a/app/assets/javascripts/notes/components/issue_note.vue
+++ b/app/assets/javascripts/notes/components/issue_note.vue
@@ -62,7 +62,7 @@
},
deleteHandler() {
// eslint-disable-next-line no-alert
- if (confirm('Are you sure you want to delete this list?')) {
+ if (confirm('Are you sure you want to delete this comment?')) {
this.isDeleting = true;
this.deleteNote(this.note)
diff --git a/app/assets/javascripts/notes/components/issue_note_form.vue b/app/assets/javascripts/notes/components/issue_note_form.vue
index 626c0f2ce18..e2539d6b89d 100644
--- a/app/assets/javascripts/notes/components/issue_note_form.vue
+++ b/app/assets/javascripts/notes/components/issue_note_form.vue
@@ -1,8 +1,9 @@
<script>
import { mapGetters } from 'vuex';
import eventHub from '../event_hub';
- import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue';
+ import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
+ import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'issueNoteForm',
@@ -39,12 +40,13 @@
};
},
components: {
- confidentialIssue,
+ issueWarning,
markdownField,
},
computed: {
...mapGetters([
'getDiscussionLastNote',
+ 'getIssueData',
'getIssueDataByProp',
'getNotesDataByProp',
'getUserDataByProp',
@@ -67,9 +69,6 @@
isDisabled() {
return !this.note.length || this.isSubmitting;
},
- isConfidentialIssue() {
- return this.getIssueDataByProp('confidential');
- },
},
methods: {
handleUpdate() {
@@ -95,6 +94,9 @@
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note);
},
},
+ mixins: [
+ issuableStateMixin,
+ ],
mounted() {
this.$refs.textarea.focus();
},
@@ -125,7 +127,13 @@
<div class="flash-container timeline-content"></div>
<form
class="edit-note common-note-form js-quick-submit gfm-form">
- <confidentialIssue v-if="isConfidentialIssue" />
+
+ <issue-warning
+ v-if="hasWarning(getIssueData)"
+ :is-locked="isLocked(getIssueData)"
+ :is-confidential="isConfidential(getIssueData)"
+ />
+
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
diff --git a/app/assets/javascripts/notes/mixins/issuable_state.js b/app/assets/javascripts/notes/mixins/issuable_state.js
new file mode 100644
index 00000000000..97f3ea0d5de
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/issuable_state.js
@@ -0,0 +1,15 @@
+export default {
+ methods: {
+ isConfidential(issue) {
+ return !!issue.confidential;
+ },
+
+ isLocked(issue) {
+ return !!issue.discussion_locked;
+ },
+
+ hasWarning(issue) {
+ return this.isConfidential(issue) || this.isLocked(issue);
+ },
+ },
+};
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index b874e484d45..c8a2f778ee8 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -1,13 +1,3 @@
-<template>
- <div class="pdf-viewer" v-if="hasPDF">
- <page v-for="(page, index) in pages"
- :key="index"
- :v-if="!loading"
- :page="page"
- :number="index + 1" />
- </div>
-</template>
-
<script>
import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
@@ -64,6 +54,16 @@
};
</script>
+<template>
+ <div class="pdf-viewer" v-if="hasPDF">
+ <page v-for="(page, index) in pages"
+ :key="index"
+ :v-if="!loading"
+ :page="page"
+ :number="index + 1" />
+ </div>
+</template>
+
<style>
.pdf-viewer {
background: url('./assets/img/bg.gif');
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
index 7b74ee4eb2e..be38f7cc129 100644
--- a/app/assets/javascripts/pdf/page/index.vue
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -1,10 +1,3 @@
-<template>
- <canvas
- class="pdf-page"
- ref="canvas"
- :data-page="number" />
-</template>
-
<script>
export default {
props: {
@@ -48,6 +41,13 @@
};
</script>
+<template>
+ <canvas
+ class="pdf-page"
+ ref="canvas"
+ :data-page="number" />
+</template>
+
<style>
.pdf-page {
margin: 8px auto 0 auto;
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index f0b44dfa6d8..9da0aac50a1 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -28,8 +28,7 @@
popoverOptions() {
return {
html: true,
- delay: { hide: 600 },
- trigger: 'hover',
+ trigger: 'focus',
placement: 'top',
title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>',
content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`,
@@ -73,8 +72,16 @@
:title="pipeline.yaml_errors">
yaml invalid
</span>
+ <span
+ v-if="pipeline.flags.failure_reason"
+ v-tooltip
+ class="js-pipeline-url-failure label label-danger"
+ :title="pipeline.failure_reason">
+ error
+ </span>
<a
v-if="pipeline.flags.auto_devops"
+ tabindex="0"
class="js-pipeline-url-autodevops label label-info autodevops-badge"
v-popover="popoverOptions"
role="button">
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 9adc15e6266..e97f5632dc8 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -97,7 +97,7 @@ export default {
postAction(endpoint) {
this.service.postAction(endpoint)
.then(() => eventHub.$emit('refreshPipelines'))
- .catch(() => new Flash('An error occured while making the request.'));
+ .catch(() => new Flash('An error occurred while making the request.'));
},
},
};
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
new file mode 100644
index 00000000000..b2b34cb83e1
--- /dev/null
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -0,0 +1,146 @@
+<script>
+ import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
+ import { __, s__, sprintf } from '../../../locale';
+ import csrf from '../../../lib/utils/csrf';
+
+ export default {
+ props: {
+ actionUrl: {
+ type: String,
+ required: true,
+ },
+ confirmWithPassword: {
+ type: Boolean,
+ required: true,
+ },
+ username: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ enteredPassword: '',
+ enteredUsername: '',
+ isOpen: false,
+ };
+ },
+ components: {
+ popupDialog,
+ },
+ computed: {
+ csrfToken() {
+ return csrf.token;
+ },
+ inputLabel() {
+ let confirmationValue;
+ if (this.confirmWithPassword) {
+ confirmationValue = __('password');
+ } else {
+ confirmationValue = __('username');
+ }
+
+ confirmationValue = `<code>${confirmationValue}</code>`;
+
+ return sprintf(
+ s__('Profiles|Type your %{confirmationValue} to confirm:'),
+ { confirmationValue },
+ false,
+ );
+ },
+ text() {
+ return sprintf(
+ s__(`Profiles|
+You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account.
+Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
+ {
+ yourAccount: `<strong>${s__('Profiles|your account')}</strong>`,
+ deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ canSubmit() {
+ if (this.confirmWithPassword) {
+ return this.enteredPassword !== '';
+ }
+
+ return this.enteredUsername === this.username;
+ },
+ onSubmit(status) {
+ if (status) {
+ if (!this.canSubmit()) {
+ return;
+ }
+
+ this.$refs.form.submit();
+ }
+
+ this.toggleOpen(false);
+ },
+ toggleOpen(isOpen) {
+ this.isOpen = isOpen;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div>
+ <popup-dialog
+ v-if="isOpen"
+ :title="s__('Profiles|Delete your account?')"
+ :text="text"
+ :kind="`danger ${!canSubmit() && 'disabled'}`"
+ :primary-button-label="s__('Profiles|Delete account')"
+ @toggle="toggleOpen"
+ @submit="onSubmit">
+
+ <template slot="body" scope="props">
+ <p v-html="props.text"></p>
+
+ <form
+ ref="form"
+ :action="actionUrl"
+ method="post">
+
+ <input
+ type="hidden"
+ name="_method"
+ value="delete" />
+ <input
+ type="hidden"
+ name="authenticity_token"
+ :value="csrfToken" />
+
+ <p id="input-label" v-html="inputLabel"></p>
+
+ <input
+ v-if="confirmWithPassword"
+ name="password"
+ class="form-control"
+ type="password"
+ v-model="enteredPassword"
+ aria-labelledby="input-label" />
+ <input
+ v-else
+ name="username"
+ class="form-control"
+ type="text"
+ v-model="enteredUsername"
+ aria-labelledby="input-label" />
+ </form>
+ </template>
+
+ </popup-dialog>
+
+ <button
+ type="button"
+ class="btn btn-danger"
+ @click="toggleOpen(true)">
+ {{ s__('Profiles|Delete account') }}
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
new file mode 100644
index 00000000000..635056e0eeb
--- /dev/null
+++ b/app/assets/javascripts/profile/account/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+
+import deleteAccountModal from './components/delete_account_modal.vue';
+
+const deleteAccountModalEl = document.getElementById('delete-account-modal');
+// eslint-disable-next-line no-new
+new Vue({
+ el: deleteAccountModalEl,
+ components: {
+ deleteAccountModal,
+ },
+ render(createElement) {
+ return createElement('delete-account-modal', {
+ props: {
+ actionUrl: deleteAccountModalEl.dataset.actionUrl,
+ confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword,
+ username: deleteAccountModalEl.dataset.username,
+ },
+ });
+ },
+});
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 291ae24aa68..4bdda611cfc 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -73,7 +73,8 @@ import _ from 'underscore';
aspectRatio: 1,
modal: true,
scalable: false,
- rotatable: false,
+ rotatable: true,
+ checkOrientation: true,
zoomable: true,
dragMode: 'move',
guides: false,
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
index 47197db39d3..65d46fa9a73 100644
--- a/app/assets/javascripts/project_fork.js
+++ b/app/assets/javascripts/project_fork.js
@@ -1,13 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
-(function() {
- this.ProjectFork = (function() {
- function ProjectFork() {
- $('.fork-thumbnail a').on('click', function() {
- $('.fork-namespaces').hide();
- return $('.save-project-loader').show();
- });
- }
+export default () => {
+ $('.js-fork-thumbnail').on('click', function forkThumbnailClicked() {
+ if ($(this).hasClass('disabled')) return false;
- return ProjectFork;
- })();
-}).call(window);
+ return $('.js-fork-content').toggle();
+ });
+};
diff --git a/app/assets/javascripts/projects_dropdown/service/projects_service.js b/app/assets/javascripts/projects_dropdown/service/projects_service.js
index fad956b4c26..9cbd8f21f2a 100644
--- a/app/assets/javascripts/projects_dropdown/service/projects_service.js
+++ b/app/assets/javascripts/projects_dropdown/service/projects_service.js
@@ -19,7 +19,7 @@ export default class ProjectsService {
getSearchedProjects(searchQuery) {
return this.projectsPath.get({
- simple: false,
+ simple: true,
per_page: 20,
membership: !!gon.current_user_id,
order_by: 'last_activity_at',
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 10da3783123..0a9fdb074e5 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,15 +1,22 @@
+import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import ProtectedBranchDropdown from './protected_branch_dropdown';
+import AccessorUtilities from '../lib/utils/accessor';
+
+const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
export default class ProtectedBranchCreate {
constructor() {
this.$form = $('.js-new-protected-branch');
+ this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+ this.currentProjectUserDefaults = {};
this.buildDropdowns();
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
+ const $protectedBranchDropdown = this.$form.find('.js-protected-branch-select');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
@@ -28,15 +35,13 @@ export default class ProtectedBranchCreate {
onSelect: this.onSelectCallback,
});
- // Select default
- $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
- $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
-
// Protected branch dropdown
this.protectedBranchDropdown = new ProtectedBranchDropdown({
- $dropdown: this.$form.find('.js-protected-branch-select'),
+ $dropdown: $protectedBranchDropdown,
onSelect: this.onSelectCallback,
});
+
+ this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown'));
}
// This will run after clicked callback
@@ -45,7 +50,41 @@ export default class ProtectedBranchCreate {
const $branchInput = this.$form.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$form.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
const $allowedToPushInput = this.$form.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
+ const completedForm = !(
+ $branchInput.val() &&
+ $allowedToMergeInput.length &&
+ $allowedToPushInput.length
+ );
+
+ this.savePreviousSelection($allowedToMergeInput.val(), $allowedToPushInput.val());
+ this.$form.find('input[type="submit"]').attr('disabled', completedForm);
+ }
+
+ loadPreviousSelection(mergeDropdown, pushDropdown) {
+ let mergeIndex = 0;
+ let pushIndex = 0;
+ if (this.isLocalStorageAvailable) {
+ const savedDefaults = JSON.parse(window.localStorage.getItem(PB_LOCAL_STORAGE_KEY));
+ if (savedDefaults != null) {
+ mergeIndex = _.findLastIndex(mergeDropdown.fullData.roles, {
+ id: parseInt(savedDefaults.mergeSelection, 0),
+ });
+ pushIndex = _.findLastIndex(pushDropdown.fullData.roles, {
+ id: parseInt(savedDefaults.pushSelection, 0),
+ });
+ }
+ }
+ mergeDropdown.selectRowAtIndex(mergeIndex);
+ pushDropdown.selectRowAtIndex(pushIndex);
+ }
- this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
+ savePreviousSelection(mergeSelection, pushSelection) {
+ if (this.isLocalStorageAvailable) {
+ const branchDefaults = {
+ mergeSelection,
+ pushSelection,
+ };
+ window.localStorage.setItem(PB_LOCAL_STORAGE_KEY, JSON.stringify(branchDefaults));
+ }
}
}
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
new file mode 100644
index 00000000000..2d8ca443ea7
--- /dev/null
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -0,0 +1,62 @@
+<script>
+ /* globals Flash */
+ import { mapGetters, mapActions } from 'vuex';
+ import '../../flash';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import store from '../stores';
+ import collapsibleContainer from './collapsible_container.vue';
+ import { errorMessages, errorMessagesTypes } from '../constants';
+
+ export default {
+ name: 'registryListApp',
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ store,
+ components: {
+ collapsibleContainer,
+ loadingIcon,
+ },
+ computed: {
+ ...mapGetters([
+ 'isLoading',
+ 'repos',
+ ]),
+ },
+ methods: {
+ ...mapActions([
+ 'setMainEndpoint',
+ 'fetchRepos',
+ ]),
+ },
+ created() {
+ this.setMainEndpoint(this.endpoint);
+ },
+ mounted() {
+ this.fetchRepos()
+ .catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS]));
+ },
+ };
+</script>
+<template>
+ <div>
+ <loading-icon
+ v-if="isLoading"
+ size="3"
+ />
+
+ <collapsible-container
+ v-else-if="!isLoading && repos.length"
+ v-for="(item, index) in repos"
+ :key="index"
+ :repo="item"
+ />
+
+ <p v-else-if="!isLoading && !repos.length">
+ {{__("No container images stored for this project. Add one by following the instructions above.")}}
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
new file mode 100644
index 00000000000..41ea9742406
--- /dev/null
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -0,0 +1,131 @@
+<script>
+ /* globals Flash */
+ import { mapActions } from 'vuex';
+ import '../../flash';
+ import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
+ import tableRegistry from './table_registry.vue';
+ import { errorMessages, errorMessagesTypes } from '../constants';
+
+ export default {
+ name: 'collapsibeContainerRegisty',
+ props: {
+ repo: {
+ type: Object,
+ required: true,
+ },
+ },
+ components: {
+ clipboardButton,
+ loadingIcon,
+ tableRegistry,
+ },
+ directives: {
+ tooltip,
+ },
+ data() {
+ return {
+ isOpen: false,
+ };
+ },
+ computed: {
+ clipboardText() {
+ return `docker pull ${this.repo.location}`;
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'fetchRepos',
+ 'fetchList',
+ 'deleteRepo',
+ ]),
+
+ toggleRepo() {
+ this.isOpen = !this.isOpen;
+
+ if (this.isOpen) {
+ this.fetchList({ repo: this.repo })
+ .catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
+ }
+ },
+
+ handleDeleteRepository() {
+ this.deleteRepo(this.repo)
+ .then(() => this.fetchRepos())
+ .catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
+ },
+
+ showError(message) {
+ Flash((errorMessages[message]));
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="container-image">
+ <div
+ class="container-image-head">
+ <button
+ type="button"
+ @click="toggleRepo"
+ class="js-toggle-repo btn-link">
+ <i
+ class="fa"
+ :class="{
+ 'fa-chevron-right': !isOpen,
+ 'fa-chevron-up': isOpen,
+ }"
+ aria-hidden="true">
+ </i>
+ {{repo.name}}
+ </button>
+
+ <clipboard-button
+ v-if="repo.location"
+ :text="clipboardText"
+ :title="repo.location"
+ />
+
+ <div class="controls hidden-xs pull-right">
+ <button
+ v-if="repo.canDelete"
+ type="button"
+ class="js-remove-repo btn btn-danger"
+ :title="s__('ContainerRegistry|Remove repository')"
+ :aria-label="s__('ContainerRegistry|Remove repository')"
+ v-tooltip
+ @click="handleDeleteRepository">
+ <i
+ class="fa fa-trash"
+ aria-hidden="true">
+ </i>
+ </button>
+ </div>
+
+ </div>
+
+ <loading-icon
+ v-if="repo.isLoading"
+ class="append-bottom-20"
+ size="2"
+ />
+
+ <div
+ v-else-if="!repo.isLoading && isOpen"
+ class="container-image-tags">
+
+ <table-registry
+ v-if="repo.list.length"
+ :repo="repo"
+ />
+
+ <div
+ v-else
+ class="nothing-here-block">
+ {{s__("ContainerRegistry|No tags in Container Registry for this container image.")}}
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
new file mode 100644
index 00000000000..4ce1571b0aa
--- /dev/null
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -0,0 +1,137 @@
+<script>
+ /* globals Flash */
+ import { mapActions } from 'vuex';
+ import { n__ } from '../../locale';
+ import '../../flash';
+ import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+ import tablePagination from '../../vue_shared/components/table_pagination.vue';
+ import tooltip from '../../vue_shared/directives/tooltip';
+ import timeagoMixin from '../../vue_shared/mixins/timeago';
+ import { errorMessages, errorMessagesTypes } from '../constants';
+
+ export default {
+ props: {
+ repo: {
+ type: Object,
+ required: true,
+ },
+ },
+ components: {
+ clipboardButton,
+ tablePagination,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
+ directives: {
+ tooltip,
+ },
+ computed: {
+ shouldRenderPagination() {
+ return this.repo.pagination.total > this.repo.pagination.perPage;
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'fetchList',
+ 'deleteRegistry',
+ ]),
+
+ layers(item) {
+ return item.layers ? n__('%d layer', '%d layers', item.layers) : '';
+ },
+
+ handleDeleteRegistry(registry) {
+ this.deleteRegistry(registry)
+ .then(() => this.fetchList({ repo: this.repo }))
+ .catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
+ },
+
+ onPageChange(pageNumber) {
+ this.fetchList({ repo: this.repo, page: pageNumber })
+ .catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
+ },
+
+ clipboardText(text) {
+ return `docker pull ${text}`;
+ },
+
+ showError(message) {
+ Flash((errorMessages[message]));
+ },
+ },
+ };
+</script>
+<template>
+<div>
+ <table class="table tags">
+ <thead>
+ <tr>
+ <th>{{s__('ContainerRegistry|Tag')}}</th>
+ <th>{{s__('ContainerRegistry|Tag ID')}}</th>
+ <th>{{s__("ContainerRegistry|Size")}}</th>
+ <th>{{s__("ContainerRegistry|Created")}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ v-for="(item, i) in repo.list"
+ :key="i">
+ <td>
+
+ {{item.tag}}
+
+ <clipboard-button
+ v-if="item.location"
+ :title="item.location"
+ :text="clipboardText(item.location)"
+ />
+ </td>
+ <td>
+ <span
+ v-tooltip
+ :title="item.revision"
+ data-placement="bottom">
+ {{item.shortRevision}}
+ </span>
+ </td>
+ <td>
+ {{item.size}}
+ <template v-if="item.size && item.layers">
+ &middot;
+ </template>
+ {{layers(item)}}
+ </td>
+
+ <td>
+ {{timeFormated(item.createdAt)}}
+ </td>
+
+ <td class="content">
+ <button
+ v-if="item.canDelete"
+ type="button"
+ class="js-delete-registry btn btn-danger hidden-xs pull-right"
+ :title="s__('ContainerRegistry|Remove tag')"
+ :aria-label="s__('ContainerRegistry|Remove tag')"
+ data-container="body"
+ v-tooltip
+ @click="handleDeleteRegistry(item)">
+ <i
+ class="fa fa-trash"
+ aria-hidden="true">
+ </i>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table-pagination
+ v-if="shouldRenderPagination"
+ :change="onPageChange"
+ :page-info="repo.pagination"
+ />
+</div>
+</template>
diff --git a/app/assets/javascripts/registry/constants.js b/app/assets/javascripts/registry/constants.js
new file mode 100644
index 00000000000..712b0fade3d
--- /dev/null
+++ b/app/assets/javascripts/registry/constants.js
@@ -0,0 +1,15 @@
+import { __ } from '../locale';
+
+export const errorMessagesTypes = {
+ FETCH_REGISTRY: 'FETCH_REGISTRY',
+ FETCH_REPOS: 'FETCH_REPOS',
+ DELETE_REPO: 'DELETE_REPO',
+ DELETE_REGISTRY: 'DELETE_REGISTRY',
+};
+
+export const errorMessages = {
+ [errorMessagesTypes.FETCH_REGISTRY]: __('Something went wrong while fetching the registry list.'),
+ [errorMessagesTypes.FETCH_REPOS]: __('Something went wrong while fetching the projects.'),
+ [errorMessagesTypes.DELETE_REPO]: __('Something went wrong on our end.'),
+ [errorMessagesTypes.DELETE_REGISTRY]: __('Something went wrong on our end.'),
+};
diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js
new file mode 100644
index 00000000000..d8edff73f72
--- /dev/null
+++ b/app/assets/javascripts/registry/index.js
@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import registryApp from './components/app.vue';
+import Translate from '../vue_shared/translate';
+
+Vue.use(Translate);
+
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#js-vue-registry-images',
+ components: {
+ registryApp,
+ },
+ data() {
+ const dataset = document.querySelector(this.$options.el).dataset;
+ return {
+ endpoint: dataset.endpoint,
+ };
+ },
+ render(createElement) {
+ return createElement('registry-app', {
+ props: {
+ endpoint: this.endpoint,
+ },
+ });
+ },
+}));
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
new file mode 100644
index 00000000000..34ed40b8b65
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+import * as types from './mutation_types';
+
+Vue.use(VueResource);
+
+export const fetchRepos = ({ commit, state }) => {
+ commit(types.TOGGLE_MAIN_LOADING);
+
+ return Vue.http.get(state.endpoint)
+ .then(res => res.json())
+ .then((response) => {
+ commit(types.TOGGLE_MAIN_LOADING);
+ commit(types.SET_REPOS_LIST, response);
+ });
+};
+
+export const fetchList = ({ commit }, { repo, page }) => {
+ commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
+
+ return Vue.http.get(repo.tagsPath, { params: { page } })
+ .then((response) => {
+ const headers = response.headers;
+
+ return response.json().then((resp) => {
+ commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
+ commit(types.SET_REGISTRY_LIST, { repo, resp, headers });
+ });
+ });
+};
+
+export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath)
+ .then(res => res.json());
+
+export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath)
+ .then(res => res.json());
+
+export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
+export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
diff --git a/app/assets/javascripts/registry/stores/getters.js b/app/assets/javascripts/registry/stores/getters.js
new file mode 100644
index 00000000000..588f479c492
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/getters.js
@@ -0,0 +1,2 @@
+export const isLoading = state => state.isLoading;
+export const repos = state => state.repos;
diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js
new file mode 100644
index 00000000000..78b67881210
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/index.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ state: {
+ isLoading: false,
+ endpoint: '', // initial endpoint to fetch the repos list
+ /**
+ * Each object in `repos` has the following strucure:
+ * {
+ * name: String,
+ * isLoading: Boolean,
+ * tagsPath: String // endpoint to request the list
+ * destroyPath: String // endpoit to delete the repo
+ * list: Array // List of the registry images
+ * }
+ *
+ * Each registry image inside `list` has the following structure:
+ * {
+ * tag: String,
+ * revision: String
+ * shortRevision: String
+ * size: Number
+ * layers: Number
+ * createdAt: String
+ * destroyPath: String // endpoit to delete each image
+ * }
+ */
+ repos: [],
+ },
+ actions,
+ getters,
+ mutations,
+});
diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js
new file mode 100644
index 00000000000..2c69bf11807
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/mutation_types.js
@@ -0,0 +1,7 @@
+export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
+
+export const SET_REPOS_LIST = 'SET_REPOS_LIST';
+export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
+
+export const SET_REGISTRY_LIST = 'SET_REGISTRY_LIST';
+export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING';
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
new file mode 100644
index 00000000000..e40382e7afc
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -0,0 +1,54 @@
+import * as types from './mutation_types';
+import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
+
+export default {
+
+ [types.SET_MAIN_ENDPOINT](state, endpoint) {
+ Object.assign(state, { endpoint });
+ },
+
+ [types.SET_REPOS_LIST](state, list) {
+ Object.assign(state, {
+ repos: list.map(el => ({
+ canDelete: !!el.destroy_path,
+ destroyPath: el.destroy_path,
+ id: el.id,
+ isLoading: false,
+ list: [],
+ location: el.location,
+ name: el.path,
+ tagsPath: el.tags_path,
+ })),
+ });
+ },
+
+ [types.TOGGLE_MAIN_LOADING](state) {
+ Object.assign(state, { isLoading: !state.isLoading });
+ },
+
+ [types.SET_REGISTRY_LIST](state, { repo, resp, headers }) {
+ const listToUpdate = state.repos.find(el => el.id === repo.id);
+
+ const normalizedHeaders = normalizeHeaders(headers);
+ const pagination = parseIntPagination(normalizedHeaders);
+
+ listToUpdate.pagination = pagination;
+
+ listToUpdate.list = resp.map(element => ({
+ tag: element.name,
+ revision: element.revision,
+ shortRevision: element.short_revision,
+ size: element.size,
+ layers: element.layers,
+ location: element.location,
+ createdAt: element.created_at,
+ destroyPath: element.destroy_path,
+ canDelete: !!element.destroy_path,
+ }));
+ },
+
+ [types.TOGGLE_REGISTRY_LIST_LOADING](state, list) {
+ const listToUpdate = state.repos.find(el => el.id === list.id);
+ listToUpdate.isLoading = !listToUpdate.isLoading;
+ },
+};
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
index d6c864cb976..cc60aa5939c 100644
--- a/app/assets/javascripts/repo/components/repo.vue
+++ b/app/assets/javascripts/repo/components/repo.vue
@@ -62,7 +62,7 @@ export default {
:primary-button-label="__('Discard changes')"
kind="warning"
:title="__('Are you sure?')"
- :body="__('Are you sure you want to discard your changes?')"
+ :text="__('Are you sure you want to discard your changes?')"
@toggle="toggleDialogOpen"
@submit="dialogSubmitted"
/>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index 1282828b504..119e38c583d 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -37,14 +37,14 @@ export default {
content: f.newContent,
}));
const payload = {
- branch: Store.targetBranch,
+ branch: Store.currentBranch,
commit_message: commitMessage,
actions,
};
Store.submitCommitsLoading = true;
Service.commitFiles(payload)
.then(this.resetCommitState)
- .catch(() => Flash('An error occured while committing your changes'));
+ .catch(() => Flash('An error occurred while committing your changes'));
},
resetCommitState() {
@@ -105,7 +105,7 @@ export default {
</label>
<div class="col-md-6">
<span class="help-block">
- {{targetBranch}}
+ {{currentBranch}}
</span>
</div>
</div>
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index 29b76975561..353142edeb7 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -26,16 +26,6 @@ export default {
this.editMode = !this.editMode;
Store.toggleBlobView();
},
- toggleProjectRefsForm() {
- $('.project-refs-form').toggleClass('disabled', this.editMode);
- $('.js-tree-ref-target-holder').toggle(this.editMode);
- },
- },
-
- watch: {
- editMode() {
- this.toggleProjectRefsForm();
- },
},
};
</script>
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
index 96d6a75bb61..02d9c775046 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -63,12 +63,7 @@ const RepoEditor = {
const lineNumber = e.target.position.lineNumber;
if (e.target.element.classList.contains('line-numbers')) {
location.hash = `L${lineNumber}`;
- Store.activeLine = lineNumber;
-
- Helper.monacoInstance.setPosition({
- lineNumber: this.activeLine,
- column: 1,
- });
+ Store.setActiveLine(lineNumber);
}
},
},
@@ -101,6 +96,15 @@ const RepoEditor = {
this.setupEditor();
}
},
+
+ activeLine() {
+ if (Helper.monacoInstance) {
+ Helper.monacoInstance.setPosition({
+ lineNumber: this.activeLine,
+ column: 1,
+ });
+ }
+ },
},
computed: {
shouldHideEditor() {
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue
index 20ebf840774..8b9cbd23456 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/repo/components/repo_file.vue
@@ -95,7 +95,7 @@ export default RepoFile;
</div>
</td>
- <td class="hidden-xs">
+ <td class="hidden-xs text-right">
<span
class="commit-update"
:title="tooltipTitle(file.lastCommitUpdate)">
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index 2200754cbef..a87bef6084a 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -1,29 +1,42 @@
<script>
+/* global LineHighlighter */
+
import Store from '../stores/repo_store';
export default {
data: () => Store,
- mounted() {
- this.highlightFile();
- },
computed: {
html() {
return this.activeFile.html;
},
},
-
methods: {
highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight();
},
+ highlightLine() {
+ if (Store.activeLine > -1) {
+ this.lineHighlighter.highlightHash(`#L${Store.activeLine}`);
+ }
+ },
+ },
+ mounted() {
+ this.highlightFile();
+ this.lineHighlighter = new LineHighlighter({
+ fileHolderSelector: '.blob-viewer-container',
+ scrollFileHolder: true,
+ });
},
-
watch: {
html() {
this.$nextTick(() => {
this.highlightFile();
+ this.highlightLine();
});
},
+ activeLine() {
+ this.highlightLine();
+ },
},
};
</script>
@@ -45,7 +58,7 @@ export default {
v-else
class="vertical-center render-error">
<p class="text-center">
- The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead.
+ The source could not be displayed because a rendering error occurred. You can <a :href="activeFile.raw_path">download</a> it instead.
</p>
</div>
</div>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
index 3414128526d..e0f3c33003a 100644
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ b/app/assets/javascripts/repo/components/repo_sidebar.vue
@@ -18,36 +18,64 @@ export default {
},
created() {
- this.addPopEventListener();
+ window.addEventListener('popstate', this.checkHistory);
+ },
+ destroyed() {
+ window.removeEventListener('popstate', this.checkHistory);
},
data: () => Store,
methods: {
- addPopEventListener() {
- window.addEventListener('popstate', () => {
- if (location.href.indexOf('#') > -1) return;
- this.linkClicked({
+ checkHistory() {
+ let selectedFile = this.files.find(file => location.pathname.indexOf(file.url) > -1);
+ if (!selectedFile) {
+ // Maybe it is not in the current tree but in the opened tabs
+ selectedFile = Helper.getFileFromPath(location.pathname);
+ }
+
+ let lineNumber = null;
+ if (location.hash.indexOf('#L') > -1) lineNumber = Number(location.hash.substr(2));
+
+ if (selectedFile) {
+ if (selectedFile.url !== this.activeFile.url) {
+ this.fileClicked(selectedFile, lineNumber);
+ } else {
+ Store.setActiveLine(lineNumber);
+ }
+ } else {
+ // Not opened at all lets open new tab
+ this.fileClicked({
url: location.href,
- });
- });
+ }, lineNumber);
+ }
},
- fileClicked(clickedFile) {
+ fileClicked(clickedFile, lineNumber) {
let file = clickedFile;
if (file.loading) return;
file.loading = true;
+
if (file.type === 'tree' && file.opened) {
file = Store.removeChildFilesOfTree(file);
file.loading = false;
+ Store.setActiveLine(lineNumber);
} else {
- Service.url = file.url;
- Helper.getContent(file)
- .then(() => {
- file.loading = false;
- Helper.scrollTabsRight();
- })
- .catch(Helper.loadingError);
+ const openFile = Helper.getFileFromPath(file.url);
+ if (openFile) {
+ file.loading = false;
+ Store.setActiveFiles(openFile);
+ Store.setActiveLine(lineNumber);
+ } else {
+ Service.url = file.url;
+ Helper.getContent(file)
+ .then(() => {
+ file.loading = false;
+ Helper.scrollTabsRight();
+ Store.setActiveLine(lineNumber);
+ })
+ .catch(Helper.loadingError);
+ }
}
},
@@ -67,8 +95,8 @@ export default {
<thead v-if="!isMini">
<tr>
<th class="name">Name</th>
- <th class="hidden-sm hidden-xs last-commit">Last Commit</th>
- <th class="hidden-xs last-update">Last Update</th>
+ <th class="hidden-sm hidden-xs last-commit">Last commit</th>
+ <th class="hidden-xs last-update text-right">Last update</th>
</tr>
</thead>
<tbody>
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
index 655e4e7605b..7483f8bc305 100644
--- a/app/assets/javascripts/repo/helpers/repo_helper.js
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -58,13 +58,13 @@ const RepoHelper = {
return langs.find(lang => lang.extensions && lang.extensions.indexOf(`.${ext}`) > -1);
},
- setDirectoryOpen(tree) {
+ setDirectoryOpen(tree, title) {
const file = tree;
if (!file) return undefined;
file.opened = true;
file.icon = 'fa-folder-open';
- RepoHelper.updateHistoryEntry(file.url, file.name);
+ RepoHelper.updateHistoryEntry(file.url, title);
return file;
},
@@ -135,6 +135,8 @@ const RepoHelper = {
return Service.getContent()
.then((response) => {
const data = response.data;
+ if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title'];
+
Store.isTree = RepoHelper.isTree(data);
if (!Store.isTree) {
if (!file) file = data;
@@ -168,7 +170,7 @@ const RepoHelper = {
} else {
// it's a tree
if (!file) Store.isRoot = RepoHelper.isRoot(Service.url);
- file = RepoHelper.setDirectoryOpen(file);
+ file = RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name);
const newDirectory = RepoHelper.dataToListOfFiles(data);
Store.addFilesToDirectory(file, Store.files, newDirectory);
Store.prevURL = Service.blobURLtoParentTree(Service.url);
@@ -252,10 +254,12 @@ const RepoHelper = {
RepoHelper.key = RepoHelper.genKey();
- history.pushState({ key: RepoHelper.key }, '', url);
+ if (document.location.pathname !== url) {
+ history.pushState({ key: RepoHelper.key }, '', url);
+ }
if (title) {
- document.title = `${title} · GitLab`;
+ document.title = title;
}
},
@@ -263,6 +267,10 @@ const RepoHelper = {
return Store.openedFiles.find(openedFile => Store.activeFile.url === openedFile.url);
},
+ getFileFromPath(path) {
+ return Store.openedFiles.find(file => file.url === path);
+ },
+
loadingError() {
Flash('Unable to load this content at this time.');
},
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
index 6c1d468e937..7d0123e3d3a 100644
--- a/app/assets/javascripts/repo/index.js
+++ b/app/assets/javascripts/repo/index.js
@@ -11,10 +11,6 @@ function initDropdowns() {
}
function addEventsForNonVueEls() {
- $(document).on('change', '.dropdown', () => {
- Store.targetBranch = $('.project-refs-target-form input[name="ref"]').val();
- });
-
window.onbeforeunload = function confirmUnload(e) {
const hasChanged = Store.openedFiles
.some(file => file.changed);
diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js
index 1c0df528aea..93b39cff27e 100644
--- a/app/assets/javascripts/repo/stores/repo_store.js
+++ b/app/assets/javascripts/repo/stores/repo_store.js
@@ -26,13 +26,12 @@ const RepoStore = {
},
activeFile: Helper.getDefaultActiveFile(),
activeFileIndex: 0,
- activeLine: 0,
+ activeLine: -1,
activeFileLabel: 'Raw',
files: [],
isCommitable: false,
binary: false,
currentBranch: '',
- targetBranch: 'new-branch',
commitMessage: '',
binaryTypes: {
png: false,
@@ -84,8 +83,9 @@ const RepoStore = {
}).catch(Helper.loadingError);
}
- if (!file.loading) Helper.updateHistoryEntry(file.url, file.name);
+ if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
RepoStore.binary = file.binary;
+ RepoStore.setActiveLine(-1);
},
setFileActivity(file, openedFile, i) {
@@ -102,6 +102,10 @@ const RepoStore = {
RepoStore.activeFileIndex = i;
},
+ setActiveLine(activeLine) {
+ if (!isNaN(activeLine)) RepoStore.activeLine = activeLine;
+ },
+
setActiveToRaw() {
RepoStore.activeFile.raw = false;
// can't get vue to listen to raw for some reason so RepoStore for now.
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index a4eae135403..a41548bd694 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -29,28 +29,32 @@ import Cookies from 'js-cookie';
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
- $document.on('click', '.js-sidebar-toggle', function(e, triggered) {
- var $allGutterToggleIcons, $this, $thisIcon;
- e.preventDefault();
- $this = $(this);
- $thisIcon = $this.find('i');
- $allGutterToggleIcons = $('.js-sidebar-toggle i');
- if ($thisIcon.hasClass('fa-angle-double-right')) {
- $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
- $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
- $('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
- } else {
- $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
- $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
- $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
- }
- if (!triggered) {
- return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
- }
- });
+ $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
+ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
+ var $allGutterToggleIcons, $this, $thisIcon;
+ e.preventDefault();
+ $this = $(this);
+ $thisIcon = $this.find('i');
+ $allGutterToggleIcons = $('.js-sidebar-toggle i');
+ if ($thisIcon.hasClass('fa-angle-double-right')) {
+ $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
+ $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ $('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ } else {
+ $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
+ $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+
+ if (gl.lazyLoader) gl.lazyLoader.loadCheck();
+ }
+ if (!triggered) {
+ Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
+ }
+ };
+
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 38c9a71dd20..f15452ec683 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -287,6 +287,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
onClearInputClick(e) {
e.preventDefault();
+ this.wrap.toggleClass('has-value', !!e.target.value);
return this.searchInput.val('').focus();
}
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index e754f6c4460..f63b99ba1c5 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -18,23 +18,8 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
Mousetrap.bind('f', (e => this.focusFilter(e)));
Mousetrap.bind('p b', this.onTogglePerfBar);
- const $globalDropdownMenu = $('.global-dropdown-menu');
- const $globalDropdownToggle = $('.global-dropdown-toggle');
const findFileURL = document.body.dataset.findFile;
- $('.global-dropdown').on('hide.bs.dropdown', () => {
- $globalDropdownMenu.removeClass('shortcuts');
- });
-
- Mousetrap.bind('n', () => {
- $globalDropdownMenu.toggleClass('shortcuts');
- $globalDropdownToggle.trigger('click');
-
- if (!$globalDropdownMenu.is(':visible')) {
- $globalDropdownToggle.blur();
- }
- });
-
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 8e7abdbffef..f2b1099a678 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -47,9 +47,9 @@ export default {
</script>
<template>
- <div class="block confidentiality">
+ <div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon">
- <i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i>
+ <i class="fa" :class="faEye" aria-hidden="true"></i>
</div>
<div class="title hide-collapsed">
Confidentiality
@@ -62,19 +62,19 @@ export default {
Edit
</a>
</div>
- <div class="value confidential-value hide-collapsed">
+ <div class="value sidebar-item-value hide-collapsed">
<editForm
v-if="edit"
:toggle-form="toggleForm"
:is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
- <div v-if="!isConfidential" class="no-value confidential-value">
- <i class="fa fa-eye is-not-confidential"></i>
+ <div v-if="!isConfidential" class="no-value sidebar-item-value">
+ <i class="fa fa-eye sidebar-item-icon"></i>
Not confidential
</div>
- <div v-else class="value confidential-value hide-collapsed">
- <i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
+ <div v-else class="value sidebar-item-value hide-collapsed">
+ <i aria-hidden="true" class="fa fa-eye-slash sidebar-item-icon is-active"></i>
This issue is confidential
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
index d578b663a54..dd17b5abd46 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue
@@ -2,9 +2,6 @@
import editFormButtons from './edit_form_buttons.vue';
export default {
- components: {
- editFormButtons,
- },
props: {
isConfidential: {
required: true,
@@ -19,12 +16,16 @@ export default {
type: Function,
},
},
+
+ components: {
+ editFormButtons,
+ },
};
</script>
<template>
<div class="dropdown open">
- <div class="dropdown-menu confidential-warning-message">
+ <div class="dropdown-menu sidebar-item-warning-message">
<div>
<p v-if="!isConfidential">
You are going to turn on the confidentiality. This means that only team members with
diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
index 97af4a3f505..7ed0619ee6b 100644
--- a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue
@@ -15,7 +15,7 @@ export default {
},
},
computed: {
- onOrOff() {
+ toggleButtonText() {
return this.isConfidential ? 'Turn Off' : 'Turn On';
},
updateConfidentialBool() {
@@ -26,7 +26,7 @@ export default {
</script>
<template>
- <div class="confidential-warning-message-actions">
+ <div class="sidebar-item-warning-message-actions">
<button
type="button"
class="btn btn-default append-right-10"
@@ -39,7 +39,7 @@ export default {
class="btn btn-close"
@click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
>
- {{ onOrOff }}
+ {{ toggleButtonText }}
</button>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
new file mode 100644
index 00000000000..c7a6edc7c70
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue
@@ -0,0 +1,61 @@
+<script>
+import editFormButtons from './edit_form_buttons.vue';
+import issuableMixin from '../../../vue_shared/mixins/issuable';
+
+export default {
+ props: {
+ isLocked: {
+ required: true,
+ type: Boolean,
+ },
+
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+
+ updateLockedAttribute: {
+ required: true,
+ type: Function,
+ },
+
+ issuableType: {
+ required: true,
+ type: String,
+ },
+ },
+
+ mixins: [
+ issuableMixin,
+ ],
+
+ components: {
+ editFormButtons,
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown open">
+ <div class="dropdown-menu sidebar-item-warning-message">
+ <p class="text" v-if="isLocked">
+ Unlock this {{ issuableDisplayName(issuableType) }}?
+ <strong>Everyone</strong>
+ will be able to comment.
+ </p>
+
+ <p class="text" v-else>
+ Lock this {{ issuableDisplayName(issuableType) }}?
+ Only
+ <strong>project members</strong>
+ will be able to comment.
+ </p>
+
+ <edit-form-buttons
+ :is-locked="isLocked"
+ :toggle-form="toggleForm"
+ :update-locked-attribute="updateLockedAttribute"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
new file mode 100644
index 00000000000..c3a553a7605
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -0,0 +1,50 @@
+<script>
+export default {
+ props: {
+ isLocked: {
+ required: true,
+ type: Boolean,
+ },
+
+ toggleForm: {
+ required: true,
+ type: Function,
+ },
+
+ updateLockedAttribute: {
+ required: true,
+ type: Function,
+ },
+ },
+
+ computed: {
+ buttonText() {
+ return this.isLocked ? this.__('Unlock') : this.__('Lock');
+ },
+
+ toggleLock() {
+ return !this.isLocked;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="sidebar-item-warning-message-actions">
+ <button
+ type="button"
+ class="btn btn-default append-right-10"
+ @click="toggleForm"
+ >
+ {{ __('Cancel') }}
+ </button>
+
+ <button
+ type="button"
+ class="btn btn-close"
+ @click.prevent="updateLockedAttribute(toggleLock)"
+ >
+ {{ buttonText }}
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
new file mode 100644
index 00000000000..c4b2900e020
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -0,0 +1,120 @@
+<script>
+/* global Flash */
+import editForm from './edit_form.vue';
+import issuableMixin from '../../../vue_shared/mixins/issuable';
+
+export default {
+ props: {
+ isLocked: {
+ required: true,
+ type: Boolean,
+ },
+
+ isEditable: {
+ required: true,
+ type: Boolean,
+ },
+
+ mediator: {
+ required: true,
+ type: Object,
+ validator(mediatorObject) {
+ return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
+ },
+ },
+
+ issuableType: {
+ required: true,
+ type: String,
+ },
+ },
+
+ mixins: [
+ issuableMixin,
+ ],
+
+ components: {
+ editForm,
+ },
+
+ computed: {
+ lockIconClass() {
+ return this.isLocked ? 'fa-lock' : 'fa-unlock';
+ },
+
+ isLockDialogOpen() {
+ return this.mediator.store.isLockDialogOpen;
+ },
+ },
+
+ methods: {
+ toggleForm() {
+ this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
+ },
+
+ updateLockedAttribute(locked) {
+ this.mediator.service.update(this.issuableType, {
+ discussion_locked: locked,
+ })
+ .then(() => location.reload())
+ .catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName(this.issuableType)}`)));
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="block issuable-sidebar-item lock">
+ <div class="sidebar-collapsed-icon">
+ <i
+ class="fa"
+ :class="lockIconClass"
+ aria-hidden="true"
+ ></i>
+ </div>
+
+ <div class="title hide-collapsed">
+ Lock {{issuableDisplayName(issuableType) }}
+ <button
+ v-if="isEditable"
+ class="pull-right lock-edit btn btn-blank"
+ type="button"
+ @click.prevent="toggleForm"
+ >
+ {{ __('Edit') }}
+ </button>
+ </div>
+
+ <div class="value sidebar-item-value hide-collapsed">
+ <edit-form
+ v-if="isLockDialogOpen"
+ :toggle-form="toggleForm"
+ :is-locked="isLocked"
+ :update-locked-attribute="updateLockedAttribute"
+ :issuable-type="issuableType"
+ />
+
+ <div
+ v-if="isLocked"
+ class="value sidebar-item-value"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-lock sidebar-item-icon is-active"
+ ></i>
+ {{ __('Locked') }}
+ </div>
+
+ <div
+ v-else
+ class="no-value sidebar-item-value hide-collapsed"
+ >
+ <i
+ aria-hidden="true"
+ class="fa fa-unlock sidebar-item-icon"
+ ></i>
+ {{ __('Unlocked') }}
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
index 1c15a1b877a..3c9de02407e 100644
--- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
+++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
@@ -38,7 +38,7 @@ class SidebarMoveIssue {
data: (searchTerm, callback) => {
this.mediator.fetchAutocompleteProjects(searchTerm)
.then(callback)
- .catch(() => new Flash('An error occured while fetching projects autocomplete.'));
+ .catch(() => new Flash('An error occurred while fetching projects autocomplete.'));
},
renderRow: project => `
<li>
@@ -73,7 +73,7 @@ class SidebarMoveIssue {
this.mediator.moveIssue()
.catch(() => {
- Flash('An error occured while moving the issue.');
+ Flash('An error occurred while moving the issue.');
this.$confirmButton
.enable()
.removeClass('is-loading');
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index 3d8972050a9..09b9d75c02d 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -1,46 +1,76 @@
import Vue from 'vue';
-import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
-import sidebarAssignees from './components/assignees/sidebar_assignees';
-import confidential from './components/confidential/confidential_issue_sidebar.vue';
+import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
+import SidebarAssignees from './components/assignees/sidebar_assignees';
+import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
+import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
+import Translate from '../vue_shared/translate';
import Mediator from './sidebar_mediator';
+Vue.use(Translate);
+
+function mountConfidentialComponent(mediator) {
+ const el = document.getElementById('js-confidential-entry-point');
+
+ if (!el) return;
+
+ const dataNode = document.getElementById('js-confidential-issue-data');
+ const initialData = JSON.parse(dataNode.innerHTML);
+
+ const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
+
+ new ConfidentialComp({
+ propsData: {
+ isConfidential: initialData.is_confidential,
+ isEditable: initialData.is_editable,
+ service: mediator.service,
+ },
+ }).$mount(el);
+}
+
+function mountLockComponent(mediator) {
+ const el = document.getElementById('js-lock-entry-point');
+
+ if (!el) return;
+
+ const dataNode = document.getElementById('js-lock-issue-data');
+ const initialData = JSON.parse(dataNode.innerHTML);
+
+ const LockComp = Vue.extend(LockIssueSidebar);
+
+ new LockComp({
+ propsData: {
+ isLocked: initialData.is_locked,
+ isEditable: initialData.is_editable,
+ mediator,
+ issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
+ },
+ }).$mount(el);
+}
+
function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions);
mediator.fetch();
- const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
- const confidentialEl = document.querySelector('#js-confidential-entry-point');
+ const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
- new Vue(sidebarAssignees).$mount(sidebarAssigneesEl);
+ new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
}
- if (confidentialEl) {
- const dataNode = document.getElementById('js-confidential-issue-data');
- const initialData = JSON.parse(dataNode.innerHTML);
+ mountConfidentialComponent(mediator);
+ mountLockComponent(mediator);
- const ConfidentialComp = Vue.extend(confidential);
-
- new ConfidentialComp({
- propsData: {
- isConfidential: initialData.is_confidential,
- isEditable: initialData.is_editable,
- service: mediator.service,
- },
- }).$mount(confidentialEl);
-
- new SidebarMoveIssue(
- mediator,
- $('.js-move-issue'),
- $('.js-move-issue-confirmation-button'),
- ).init();
- }
+ new SidebarMoveIssue(
+ mediator,
+ $('.js-move-issue'),
+ $('.js-move-issue-confirmation-button'),
+ ).init();
- new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
+ new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
}
document.addEventListener('DOMContentLoaded', domContentLoaded);
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index e38a8db4cc5..2fe6e5b31f0 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -41,7 +41,7 @@ export default class SidebarMediator {
this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data);
})
- .catch(() => new Flash('Error occured when fetching sidebar data'));
+ .catch(() => new Flash('Error occurred when fetching sidebar data'));
}
fetchAutocompleteProjects(searchTerm) {
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index cc04a2a3fcf..d5d04103f3f 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -15,6 +15,7 @@ export default class SidebarStore {
};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
+ this.isLockDialogOpen = false;
SidebarStore.singleton = this;
}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 4505a79a2df..3f811c59cb9 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
import FilesCommentButton from './files_comment_button';
+import imageDiffHelper from './image_diff/helpers/index';
const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
@@ -74,7 +75,11 @@ export default class SingleFileDiff {
gl.diffNotesCompileComponents();
}
- FilesCommentButton.init($(_this.file));
+ const $file = $(_this.file);
+ FilesCommentButton.init($file);
+
+ const canCreateNote = $file.closest('.files').is('[data-can-create-note]');
+ imageDiffHelper.initImageDiff($file[0], canCreateNote);
if (cb) cb();
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js
index 703f3a56a34..4998a47b691 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js
@@ -27,7 +27,7 @@ export default {
<button
v-if="showDisabledButton"
type="button"
- class="btn btn-success btn-sm"
+ class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true">
Merge
</button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
index aaf9d3304a4..09561694939 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="loading" showDisabledButton />
+ <status-icon status="loading" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Checking ability to merge automatically
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
index 4078aad7f83..b25cc3443ef 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
@@ -16,9 +16,9 @@ export default {
<div class="media-body">
<mr-widget-author-and-time
actionText="Closed by"
- :author="mr.closedBy"
- :dateTitle="mr.updatedAt"
- :dateReadable="mr.closedAt"
+ :author="mr.closedEvent.author"
+ :dateTitle="mr.closedEvent.updatedAt"
+ :dateReadable="mr.closedEvent.formattedUpdatedAt"
/>
<section class="mr-info-list">
<p>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
index f9cb79a0bc1..5d468a085cb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
@@ -10,27 +10,37 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon
+ status="failed"
+ :show-disabled-button="true" />
<div class="media-body space-children">
- <span class="bold">
- There are merge conflicts<span v-if="!mr.canMerge">.</span>
- <span v-if="!mr.canMerge">
- Resolve these conflicts or ask someone with write access to this repository to merge it locally
- </span>
+ <span
+ v-if="mr.shouldBeRebased"
+ class="bold">
+ Fast-forward merge is not possible.
+ To merge this request, first rebase locally.
</span>
- <a
- v-if="mr.canMerge && mr.conflictResolutionPath"
- :href="mr.conflictResolutionPath"
- class="btn btn-default btn-xs js-resolve-conflicts-button">
- Resolve conflicts
- </a>
- <a
- v-if="mr.canMerge"
- class="btn btn-default btn-xs js-merge-locally-button"
- data-toggle="modal"
- href="#modal_merge_info">
- Merge locally
- </a>
+ <template v-else>
+ <span class="bold">
+ There are merge conflicts<span v-if="!mr.canMerge">.</span>
+ <span v-if="!mr.canMerge">
+ Resolve these conflicts or ask someone with write access to this repository to merge it locally
+ </span>
+ </span>
+ <a
+ v-if="mr.canMerge && mr.conflictResolutionPath"
+ :href="mr.conflictResolutionPath"
+ class="js-resolve-conflicts-button btn btn-default btn-xs">
+ Resolve conflicts
+ </a>
+ <a
+ v-if="mr.canMerge"
+ class="js-merge-locally-button btn btn-default btn-xs"
+ data-toggle="modal"
+ href="#modal_merge_info">
+ Merge locally
+ </a>
+ </template>
</div>
</div>
`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
index 1cb24549d53..c25d6c359bb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.js
@@ -51,7 +51,7 @@ export default {
</span>
</template>
<template v-else>
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
index e452260a4d0..74fc52796a0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
@@ -69,9 +69,9 @@ export default {
<div class="space-children">
<mr-widget-author-and-time
actionText="Merged by"
- :author="mr.mergedBy"
- :dateTitle="mr.updatedAt"
- :dateReadable="mr.mergedAt" />
+ :author="mr.mergedEvent.author"
+ :date-title="mr.mergedEvent.updatedAt"
+ :date-readable="mr.mergedEvent.formattedUpdatedAt" />
<a
v-if="mr.canRevertInCurrentMR"
v-tooltip
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
index 9f0a359d01a..1bc0b7e0819 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js
@@ -24,7 +24,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold js-branch-text">
<span class="capitalize">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
index 797511d4e3a..00047718201 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="success" showDisabledButton />
+ <status-icon status="success" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Ready to be merged automatically.
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
index 167a0d4613a..1cedf86e811 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
index c5be9a0530a..6853ba4b9f8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index ad709da51ee..61734163b6e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -38,24 +38,40 @@ export default {
return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
},
- mergeButtonClass() {
- const defaultClass = 'btn btn-sm btn-success accept-merge-request';
- const failedClass = `${defaultClass} btn-danger`;
- const inActionClass = `${defaultClass} btn-info`;
+ status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) {
- return failedClass;
+ return 'failed';
} else if (!pipeline) {
- return defaultClass;
+ return 'success';
} else if (isPipelineActive) {
- return inActionClass;
+ return 'pending';
} else if (isPipelineFailed) {
+ return 'failed';
+ }
+
+ return 'success';
+ },
+ mergeButtonClass() {
+ const defaultClass = 'btn btn-sm btn-success accept-merge-request';
+ const failedClass = `${defaultClass} btn-danger`;
+ const inActionClass = `${defaultClass} btn-info`;
+
+ if (this.status === 'failed') {
return failedClass;
+ } else if (this.status === 'pending') {
+ return inActionClass;
}
return defaultClass;
},
+ iconClass() {
+ if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) {
+ return 'failed';
+ }
+ return 'success';
+ },
mergeButtonText() {
if (this.isMergingImmediately) {
return 'Merge in progress';
@@ -84,13 +100,8 @@ export default {
},
},
methods: {
- isMergeAllowed() {
- return !this.mr.onlyAllowMergeIfPipelineSucceeds ||
- this.mr.isPipelinePassing ||
- this.mr.isPipelineSkipped;
- },
shouldShowMergeControls() {
- return this.isMergeAllowed() || this.shouldShowMergeWhenPipelineSucceedsText;
+ return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
},
updateCommitMessage() {
const cmwd = this.mr.commitMessageWithDescription;
@@ -156,6 +167,7 @@ export default {
eventHub.$emit('FetchActionsContent');
if (window.mergeRequest) {
window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
+ window.mergeRequest.hideCloseButton();
window.mergeRequest.decreaseCounter();
}
stopPolling();
@@ -208,7 +220,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="success" />
+ <status-icon :status="iconClass" />
<div class="media-body">
<div class="mr-widget-body-controls media space-children">
<span class="btn-group append-bottom-5">
@@ -284,10 +296,16 @@ export default {
:mr="mr"
:is-merge-button-disabled="isMergeButtonDisabled" />
+ <span
+ v-if="mr.ffOnlyEnabled"
+ class="js-fast-forward-message">
+ Fast-forward merge without a merge commit
+ </span>
<button
+ v-else
@click="toggleCommitMessageEditor"
:disabled="isMergeButtonDisabled"
- class="btn btn-default btn-xs"
+ class="js-modify-commit-message-button btn btn-default btn-xs"
type="button">
Modify commit message
</button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
index 89f38e5bd2a..af19cf6ab87 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_sha_mismatch.js
@@ -7,7 +7,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
index d762ca6e640..a119ecbbdfe 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
@@ -10,7 +10,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" showDisabledButton />
+ <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
There are unresolved discussions. Please resolve these discussions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
index b11a06899cf..54be1fbe675 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
@@ -38,7 +38,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
- <status-icon status="failed" :showDisabledButton="Boolean(mr.removeWIPPath)" />
+ <status-icon status="failed" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
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 29464662578..c1f7e64f580 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
@@ -37,10 +37,8 @@ export default class MergeRequestStore {
}
this.updatedAt = data.updated_at;
- this.mergedAt = MergeRequestStore.getEventDate(data.merge_event);
- this.closedAt = MergeRequestStore.getEventDate(data.closed_event);
- this.mergedBy = MergeRequestStore.getAuthorObject(data.merge_event);
- this.closedBy = MergeRequestStore.getAuthorObject(data.closed_event);
+ this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event);
+ this.closedEvent = MergeRequestStore.getEventObject(data.closed_event);
this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} });
this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id;
@@ -57,6 +55,8 @@ export default class MergeRequestStore {
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
this.mergePath = data.merge_path;
+ this.ffOnlyEnabled = data.ff_only_enabled;
+ this.shouldBeRebased = !!data.should_be_rebased;
this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
@@ -73,6 +73,7 @@ export default class MergeRequestStore {
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
+ this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
// Cherry-pick and Revert actions related
@@ -118,6 +119,14 @@ export default class MergeRequestStore {
}
}
+ static getEventObject(event) {
+ return {
+ author: MergeRequestStore.getAuthorObject(event),
+ updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
+ formattedUpdatedAt: MergeRequestStore.getEventDate(event),
+ };
+ }
+
static getAuthorObject(event) {
if (!event) {
return {};
@@ -131,6 +140,14 @@ export default class MergeRequestStore {
};
}
+ static getEventUpdatedAtDate(event) {
+ if (!event) {
+ return '';
+ }
+
+ return event.updated_at;
+ }
+
static getEventDate(event) {
const timeagoInstance = new Timeago();
@@ -138,7 +155,7 @@ export default class MergeRequestStore {
return '';
}
- return timeagoInstance.format(event.updated_at);
+ return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event));
}
}
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
new file mode 100644
index 00000000000..3a7143c450e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -0,0 +1,32 @@
+<script>
+ /**
+ * Falls back to the code used in `copy_to_clipboard.js`
+ */
+
+ export default {
+ name: 'clipboardButton',
+ props: {
+ text: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ },
+ };
+</script>
+
+<template>
+ <button
+ type="button"
+ class="btn btn-transparent btn-clipboard"
+ :data-title="title"
+ :data-clipboard-text="text">
+ <i
+ aria-hidden="true"
+ class="fa fa-clipboard">
+ </i>
+ </button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/confidential_issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/confidential_issue_warning.vue
deleted file mode 100644
index 397d16331d5..00000000000
--- a/app/assets/javascripts/vue_shared/components/issue/confidential_issue_warning.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-<script>
- export default {
- name: 'confidentialIssueWarning',
- };
-</script>
-<template>
- <div class="confidential-issue-warning">
- <i
- aria-hidden="true"
- class="fa fa-eye-slash">
- </i>
- <span>
- This is a confidential issue. Your comment will not be visible to the public.
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
new file mode 100644
index 00000000000..16c0a8efcd2
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -0,0 +1,55 @@
+<script>
+ export default {
+ props: {
+ isLocked: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+
+ isConfidential: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+
+ computed: {
+ iconClass() {
+ return {
+ 'fa-eye-slash': this.isConfidential,
+ 'fa-lock': this.isLocked,
+ };
+ },
+
+ isLockedAndConfidential() {
+ return this.isConfidential && this.isLocked;
+ },
+ },
+ };
+</script>
+<template>
+ <div class="issuable-note-warning">
+ <i
+ aria-hidden="true"
+ class="fa icon"
+ :class="iconClass"
+ v-if="!isLockedAndConfidential"
+ ></i>
+
+ <span v-if="isLockedAndConfidential">
+ {{ __('This issue is confidential and locked.') }}
+ {{ __('People without permission will never get a notification and won\'t be able to comment.') }}
+ </span>
+
+ <span v-else-if="isConfidential">
+ {{ __('This is a confidential issue.') }}
+ {{ __('Your comment will not be visible to the public.') }}
+ </span>
+
+ <span v-else-if="isLocked">
+ {{ __('This issue is locked.') }}
+ {{ __('Only project members can comment.') }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
index 994b33bc1c9..9279b50cd55 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -7,7 +7,7 @@ export default {
type: String,
required: true,
},
- body: {
+ text: {
type: String,
required: true,
},
@@ -63,7 +63,9 @@ export default {
<h4 class="modal-title">{{this.title}}</h4>
</div>
<div class="modal-body">
- <p>{{this.body}}</p>
+ <slot name="body" :text="text">
+ <p>{{text}}</p>
+ </slot>
</div>
<div class="modal-footer">
<button
diff --git a/app/assets/javascripts/vue_shared/mixins/issuable.js b/app/assets/javascripts/vue_shared/mixins/issuable.js
new file mode 100644
index 00000000000..263361587e0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/issuable.js
@@ -0,0 +1,9 @@
+export default {
+ methods: {
+ issuableDisplayName(issuableType) {
+ const displayName = issuableType.replace(/_/, ' ');
+
+ return this.__ ? this.__(displayName) : displayName;
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js
index f83c4b00761..2c7886ec308 100644
--- a/app/assets/javascripts/vue_shared/translate.js
+++ b/app/assets/javascripts/vue_shared/translate.js
@@ -2,6 +2,7 @@ import {
__,
n__,
s__,
+ sprintf,
} from '../locale';
export default (Vue) => {
@@ -37,6 +38,7 @@ export default (Vue) => {
@returns {String} Translated context based text
**/
s__,
+ sprintf,
},
});
};
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 74b846217bb..c7be94e2c8e 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -30,16 +30,16 @@
@import "framework/media_object";
@import "framework/mobile";
@import "framework/modal";
-@import "framework/nav";
-@import "framework/new-nav";
@import "framework/pagination";
@import "framework/panels";
+@import "framework/secondary-navigation-elements";
@import "framework/selects";
@import "framework/sidebar";
@import "framework/new-sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/timeline";
+@import "framework/tooltips";
@import "framework/typography";
@import "framework/zen";
@import "framework/blank";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 667b73e150d..f0e6b23757f 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -115,8 +115,7 @@
@return $unfoldedTransition;
}
-.btn,
-.global-dropdown-toggle {
+.btn {
@include transition(background-color, border-color, color, box-shadow);
}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index bdcbd4021b3..f1aedc227f3 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -23,6 +23,7 @@
&.s60 { @include avatar-size(60px, 12px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
+ &.s100 { @include avatar-size(100px, 15px); }
&.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); }
@@ -78,6 +79,7 @@
&.s60 { font-size: 32px; line-height: 58px; }
&.s70 { font-size: 34px; line-height: 70px; }
&.s90 { font-size: 36px; line-height: 88px; }
+ &.s100 { font-size: 36px; line-height: 98px; }
&.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; }
&.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 1d72a70f0f5..dbd990f84c1 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -207,6 +207,16 @@
&.user-cover-block {
padding: 24px 0 0;
+
+ .nav-links {
+ justify-content: center;
+ width: 100%;
+ float: none;
+
+ &.scrolling-tabs {
+ float: none;
+ }
+ }
}
.group-info {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index d178bc17462..b131e2d57ee 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,3 +1,25 @@
+@mixin btn-comment-icon {
+ border-radius: 50%;
+ background: $white-light;
+ padding: 1px 5px;
+ font-size: 12px;
+ color: $blue-500;
+ width: 23px;
+ height: 23px;
+ border: 1px solid $blue-500;
+
+ &:hover,
+ &.inverted {
+ background: $blue-500;
+ border-color: $blue-600;
+ color: $white-light;
+ }
+
+ &:active {
+ outline: 0;
+ }
+}
+
@mixin btn-default {
border-radius: 3px;
font-size: $gl-font-size;
@@ -381,7 +403,11 @@
background: transparent;
border: 0;
+ &:hover,
+ &:active,
&:focus {
outline: 0;
+ background: transparent;
+ box-shadow: none;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 706a9cffe87..96f9dda26c4 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px; }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px; }
+.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
@@ -129,11 +130,6 @@ span.update-author {
}
}
-.user-mention {
- color: $user-mention-color;
- font-weight: $gl-font-weight-bold;
-}
-
.field_with_errors {
display: inline;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index c0d8e6c328c..5b950ae0ba0 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -745,7 +745,11 @@
#{$selector}.dropdown-menu-nav {
margin-bottom: 24px;
- li {
+ &.dropdown-open-top {
+ margin-bottom: $dropdown-vertical-offset;
+ }
+
+ li:not(.dropdown-bold-header) {
display: block;
padding: 0 1px;
@@ -873,12 +877,19 @@
min-width: 100%;
}
}
+
+ header.navbar-gitlab-new .header-content .dropdown {
+ .dropdown-menu {
+ left: 0;
+ min-width: 100%;
+ }
+ }
}
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + ');
-header.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu {
+header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0;
}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index dbdd5a4464b..34a35734acc 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -6,3 +6,14 @@
.gfm-commit_range {
@extend .commit-sha;
}
+
+.gfm-project_member {
+ padding: 0 2px;
+ border-radius: #{$border-radius-default / 2};
+ background-color: $user-mention-bg;
+
+ &:hover {
+ background-color: $user-mention-bg-hover;
+ text-decoration: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 0d1c04026b8..52b87de7a3d 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -5,7 +5,7 @@
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) {
// Header
- header.navbar-gitlab-new {
+ .navbar-gitlab {
background-color: $color-900;
.navbar-collapse {
@@ -95,7 +95,7 @@
}
}
- .title {
+ .navbar .title {
> a {
&:hover,
&:focus {
@@ -126,7 +126,7 @@
.search-input-wrap {
.search-icon,
.clear-icon {
- color: rgba($color-200, .8);
+ fill: rgba($color-200, .8);
}
}
@@ -141,7 +141,7 @@
.search-input-wrap {
.search-icon {
- color: rgba($color-200, .8);
+ fill: rgba($color-200, .8);
}
}
}
@@ -200,9 +200,9 @@ body {
&.ui_light {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
- header.navbar-gitlab-new {
+ .navbar-gitlab {
background-color: $theme-gray-100;
- box-shadow: 0 2px 0 0 $border-color;
+ box-shadow: 0 1px 0 0 $border-color;
.logo-text svg {
fill: $theme-gray-900;
@@ -252,7 +252,7 @@ body {
.search-input-wrap {
.search-icon {
- color: $theme-gray-200;
+ fill: $theme-gray-200;
}
.search-input {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ab3c34df1fb..22945e935ef 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -1,195 +1,155 @@
-/*
- * Application Header
- *
- */
+.content-wrapper.page-with-new-nav {
+ margin-top: $header-height;
+}
-header {
+.navbar-gitlab {
@include new-style-dropdown;
- transition: padding $sidebar-transition-duration;
-
- &.navbar-empty {
- height: $header-height;
- background: $white-light;
- border-bottom: 1px solid $white-normal;
-
- .center-logo {
- margin: 8px 0;
- text-align: center;
-
- .tanuki-logo,
- img {
- height: 36px;
- }
- }
- }
-
&.navbar-gitlab {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
min-height: $header-height;
- background-color: $gray-light;
border: none;
border-bottom: 1px solid $border-color;
position: fixed;
top: 0;
left: 0;
right: 0;
- color: $gl-text-color-secondary;
border-radius: 0;
- @media (max-width: $screen-xs-min) {
- padding: 0 16px;
- }
-
- &.with-horizontal-nav {
- border-bottom: 0;
+ .logo-text {
+ line-height: initial;
- .navbar-border {
- height: 1px;
- position: absolute;
- right: 0;
- left: 0;
- bottom: -1px;
- background-color: $border-color;
- opacity: 0;
+ svg {
+ width: 55px;
+ height: 14px;
+ margin: 0;
+ fill: $white-light;
}
}
.container-fluid {
- width: 100% !important;
- filter: none;
padding: 0;
- .nav > li > a {
- color: currentColor;
- font-size: 18px;
- padding: 0;
- margin: (($header-height - 28) / 2) 3px;
- margin-left: 8px;
- height: 28px;
- min-width: 32px;
- line-height: 28px;
- text-align: center;
-
- &.header-user-dropdown-toggle {
- margin-left: 14px;
-
- &:hover,
- &:focus,
- &:active {
- .header-user-avatar {
- border-color: rgba($avatar-border, .2);
- }
- }
- }
-
- &:hover,
- &:focus,
- &:active {
- background-color: transparent;
- color: $gl-text-color;
-
- svg {
- fill: $gl-text-color;
- }
- }
-
- .fa-caret-down {
- font-size: 14px;
- }
-
- .fa-chevron-down {
- position: relative;
- top: -3px;
- font-size: 10px;
- }
- }
-
.user-counter {
svg {
- height: 16px;
- width: 23px;
+ margin-right: 3px;
}
}
.navbar-toggle {
- color: $nav-toggle-gray;
- margin: 5px 0;
- border-radius: 0;
right: -10px;
- padding: 6px 10px;
+ border-radius: 0;
+ min-width: 45px;
+ padding: 0;
+ margin-right: -7px;
+ font-size: 14px;
+ text-align: center;
+ color: currentColor;
- &:hover {
- background-color: $white-normal;
+ &:hover,
+ &:focus,
+ &.active {
+ color: currentColor;
+ background-color: transparent;
}
- &.active {
- color: $gl-text-color-secondary;
+ .more-icon,
+ .close-icon {
+ fill: $white-light;
+ margin: auto;
}
}
}
}
- &.navbar-gitlab-new {
- .fa-times {
+ .close-icon {
+ display: none;
+ }
+
+ .menu-expanded {
+ .more-icon {
display: none;
}
- .menu-expanded {
- .fa-ellipsis-v {
- display: none;
- }
-
- .fa-times {
- display: block;
- }
+ .close-icon {
+ display: block;
}
}
- .global-dropdown {
- position: absolute;
- left: -10px;
+ .header-content {
+ display: -webkit-flex;
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ min-height: $header-height;
+ padding-left: 0;
- .badge {
- font-size: 11px;
+ .title-container {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-align-items: stretch;
+ align-items: stretch;
+ -webkit-flex: 1 1 auto;
+ flex: 1 1 auto;
+ padding-top: 0;
+ overflow: visible;
}
- li {
- &.active a {
- font-weight: $gl-font-weight-bold;
+ .title {
+ padding-right: 0;
+ color: currentColor;
+ display: -webkit-flex;
+ display: flex;
+ position: relative;
+ margin: 0;
+ font-size: 18px;
+ vertical-align: top;
+ white-space: nowrap;
+
+ img {
+ height: 28px;
+ margin-right: 8px;
+ }
+
+ &.wrap {
+ white-space: normal;
}
- }
- }
- .global-dropdown-toggle {
- margin: 7px 0;
- font-size: 18px;
- padding: 6px 10px;
- border: none;
- background-color: $gray-light;
+ &.initializing {
+ opacity: 0;
+ }
- &:hover {
- background-color: $white-normal;
- }
+ a {
+ display: -webkit-flex;
+ display: flex;
+ align-items: center;
+ padding: 2px 8px;
+ margin: 5px 2px 5px -8px;
+ border-radius: $border-radius-default;
- &:focus {
- outline: none;
- background-color: $white-normal;
+ svg {
+ @media (min-width: $screen-sm-min) {
+ margin-right: 8px;
+ }
+ }
+ }
+
+ .project-item-select {
+ right: auto;
+ left: 0;
+ }
}
- }
- .header-content {
- display: flex;
- justify-content: space-between;
- position: relative;
- min-height: $header-height;
- padding-left: 30px;
+ .dropdown.open {
+ > a {
+ border-bottom-color: $white-light;
+ }
+ }
&.menu-expanded {
@media (max-width: $screen-xs-max) {
- .header-logo,
.title-container {
display: none;
}
@@ -199,111 +159,180 @@ header {
}
}
}
+ }
- .dropdown-menu {
- margin-top: -5px;
- }
+ .dropdown-bold-header {
+ color: $gl-text-color-secondary;
+ font-size: 12px;
+ }
- .header-logo {
- display: inline-block;
- margin: 0 12px 0 2px;
- position: relative;
- top: 10px;
- transition-duration: .3s;
+ .navbar-collapse {
+ flex: 0 0 auto;
+ border-top: none;
+ padding: 0;
- svg,
- img {
- height: 28px;
- }
+ @media (max-width: $screen-xs-max) {
+ flex: 1 1 auto;
+ }
- &:hover {
- cursor: pointer;
+ .nav {
+ > li:not(.hidden-xs) a {
+ @media (max-width: $screen-xs-max) {
+ margin-left: 0;
+ min-width: 100%;
+ }
}
}
+ }
- .group-name-toggle {
- margin: 3px 5px;
- }
+ .container-fluid {
- .group-title {
- &.is-hidden {
- .hidable:not(:last-of-type) {
- display: none;
+ .navbar-nav {
+ @media (max-width: $screen-xs-max) {
+ display: -webkit-flex;
+ display: flex;
+ padding-right: 10px;
+ }
+
+ li {
+ .badge {
+ box-shadow: none;
+ font-weight: $gl-font-weight-bold;
}
}
}
- .title-container {
- display: flex;
- align-items: flex-start;
- flex: 1 1 auto;
- padding-top: 14px;
- overflow: hidden;
- }
+ .nav > li {
+ &.header-user {
+ @media (max-width: $screen-xs-max) {
+ padding-left: 10px;
+ }
+ }
- .title {
- position: relative;
- padding-right: 20px;
- margin: 0;
- font-size: 18px;
- line-height: 22px;
- display: inline-block;
- font-weight: $gl-font-weight-normal;
- color: $gl-text-color;
- vertical-align: top;
- white-space: nowrap;
+ > a {
+ will-change: color;
+ margin: 4px 2px;
+ padding: 6px 8px;
+ height: 32px;
- &.wrap {
- white-space: normal;
+ @media (max-width: $screen-xs-max) {
+ padding: 0;
+ }
+
+ &.header-user-dropdown-toggle {
+ margin-left: 2px;
+
+ .header-user-avatar {
+ margin-right: 0;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ outline: 0;
+ opacity: 1;
+ color: $white-light;
+
+ svg {
+ fill: currentColor;
+ }
+
+ &.header-user-dropdown-toggle {
+ .header-user-avatar {
+ border-color: $white-light;
+ }
+ }
+ }
}
- &.initializing {
- opacity: 0;
+ .header-new-dropdown-toggle {
+ margin-right: 0;
}
- a {
- color: currentColor;
+ .impersonated-user,
+ .impersonated-user:hover {
+ margin-right: 1px;
+ background-color: $white-light;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ .impersonation-btn,
+ .impersonation-btn:hover {
+ background-color: $white-light;
+ margin-left: 0;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
- &:hover {
- text-decoration: underline;
- color: $gl-header-nav-hover-color;
+ i {
+ color: $orange-500;
+ font-size: 20px;
}
}
- .dropdown-toggle-caret {
- color: $gl-text-color;
- border: transparent;
- background: transparent;
- position: absolute;
- top: 2px;
- right: 3px;
- width: 12px;
- line-height: 19px;
- padding: 0;
- font-size: 10px;
- text-align: center;
- cursor: pointer;
+ &.active > a,
+ &.dropdown.open > a {
- &:hover {
- color: $gl-header-nav-hover-color;
+ svg {
+ fill: currentColor;
}
}
+ }
+ }
+}
- .project-item-select {
- right: auto;
- left: 0;
+.navbar-sub-nav,
+.navbar-nav {
+ > li {
+ > a:hover,
+ > a:focus {
+ text-decoration: none;
+ outline: 0;
+ color: $white-light;
+
+ svg {
+ fill: currentColor;
}
}
- .navbar-collapse {
- flex: 0 0 auto;
- border-top: none;
- padding: 0;
-
- @media (max-width: $screen-xs-max) {
- flex: 1 1 auto;
+ > a {
+ display: -webkit-flex;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 8px;
+ margin: 4px 2px;
+ font-size: 12px;
+ color: currentColor;
+ border-radius: $border-radius-default;
+ height: 32px;
+ font-weight: $gl-font-weight-bold;
+
+ svg {
+ fill: currentColor;
}
}
+
+ &.line-separator {
+ margin: 8px;
+ }
+ }
+}
+
+.navbar-sub-nav {
+ display: -webkit-flex;
+ display: flex;
+ margin: 0 0 0 6px;
+
+ .projects-dropdown-menu {
+ padding: 0;
+ }
+
+ .dropdown-chevron {
+ position: relative;
+ top: -1px;
+ font-size: 10px;
}
.project-item-select-holder {
@@ -315,8 +344,123 @@ header {
}
}
-.with-performance-bar header.navbar-gitlab {
- top: $performance-bar-height;
+.caret-down {
+ height: 11px;
+ width: 11px;
+ margin-left: 4px;
+ fill: currentColor;
+}
+
+.header-user .dropdown-menu-nav,
+.header-new .dropdown-menu-nav {
+ margin-top: $dropdown-vertical-offset;
+}
+
+.breadcrumbs {
+ display: -webkit-flex;
+ display: flex;
+ min-height: 48px;
+ color: $gl-text-color;
+}
+
+.breadcrumbs-container {
+ display: -webkit-flex;
+ display: flex;
+ width: 100%;
+ position: relative;
+ padding-top: $gl-padding / 2;
+ padding-bottom: $gl-padding / 2;
+ align-items: center;
+ border-bottom: 1px solid $border-color;
+}
+
+.breadcrumbs-links {
+ -webkit-flex: 1;
+ flex: 1;
+ min-width: 0;
+ align-self: center;
+ color: $gl-text-color-secondary;
+
+ .avatar-tile {
+ margin-right: 4px;
+ border: 1px solid $border-color;
+ border-radius: 50%;
+ vertical-align: sub;
+ }
+
+ .text-expander {
+ margin-left: 0;
+ margin-right: 2px;
+
+ > i {
+ position: relative;
+ top: 1px;
+ }
+ }
+}
+
+.breadcrumbs-list {
+ display: -webkit-flex;
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 0;
+ line-height: 16px;
+
+ > li {
+ display: flex;
+ align-items: center;
+ position: relative;
+ padding: 2px 0;
+
+ &:not(:last-child) {
+ margin-right: 20px;
+ }
+
+ > a {
+ font-size: 12px;
+ color: currentColor;
+ }
+ }
+}
+
+.breadcrumb-item-text {
+ @include str-truncated(128px);
+ text-decoration: inherit;
+}
+
+.breadcrumbs-list-angle {
+ position: absolute;
+ right: -12px;
+ top: 50%;
+ color: $gl-text-color-tertiary;
+ transform: translateY(-50%);
+}
+
+.breadcrumbs-extra {
+ display: -webkit-flex;
+ display: flex;
+ flex: 0 0 auto;
+ 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 {
+ margin-top: 3px;
+ font-weight: $gl-font-weight-bold;
+
+ &:hover {
+ background-color: $white-light;
+ }
}
.navbar-nav {
@@ -348,11 +492,10 @@ header {
}
@media (max-width: $screen-xs-max) {
- header .container-fluid {
+ .navbar-gitlab .container-fluid {
font-size: 18px;
.navbar-nav {
- display: table;
table-layout: fixed;
width: 100%;
margin: 0;
@@ -360,7 +503,8 @@ header {
}
.navbar-collapse {
- padding-left: 5px;
+ margin-left: -8px;
+ margin-right: -10px;
.nav > li:not(.hidden-xs) {
display: table-cell !important;
@@ -386,11 +530,11 @@ header {
.dropdown-menu-nav {
width: auto;
min-width: 140px;
- margin-top: -5px;
+ margin-top: 4px;
color: $gl-text-color;
left: auto;
- .current-user {
+ li.current-user {
padding: 5px 18px;
.user-name {
@@ -406,3 +550,23 @@ header {
border-radius: 50%;
border: 1px solid $avatar-border;
}
+
+.with-performance-bar .navbar-gitlab {
+ top: $performance-bar-height;
+}
+
+.navbar-empty {
+ height: $header-height;
+ background: $white-light;
+ border-bottom: 1px solid $white-normal;
+
+ .center-logo {
+ margin: 8px 0;
+ text-align: center;
+
+ .tanuki-logo,
+ img {
+ height: 36px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 60d61c68d63..6819fd88b7f 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -27,7 +27,10 @@
}
svg {
+ &.s8 { @include svg-size(8px); }
+ &.s12 { @include svg-size(12px); }
&.s16 { @include svg-size(16px); }
+ &.s18 { @include svg-size(18px); }
&.s24 { @include svg-size(24px); }
&.s32 { @include svg-size(32px); }
&.s48 { @include svg-size(48px); }
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index bd521028c44..69d19ea2962 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -25,10 +25,6 @@ body {
.content-wrapper {
padding-bottom: 100px;
-
- &:not(.page-with-layout-nav) {
- margin-top: $header-height;
- }
}
.container {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 0fb19344510..badc7b0eba3 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -229,6 +229,10 @@ ul.content-list {
.label-default {
color: $gl-text-color-secondary;
}
+
+ .avatar-cell {
+ align-self: flex-start;
+ }
}
.panel > .content-list > li {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 5b581780447..1cebd02df48 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -1,10 +1,17 @@
+.modal-header {
+ padding: #{3 * $grid-size} #{2 * $grid-size};
+
+ .page-title {
+ margin-top: 0;
+ }
+}
+
.modal-body {
position: relative;
- padding: 15px;
+ padding: #{3 * $grid-size} #{2 * $grid-size};
.form-actions {
- margin: -$gl-padding + 1;
- margin-top: 15px;
+ margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
}
.text-danger {
diff --git a/app/assets/stylesheets/framework/new-nav.scss b/app/assets/stylesheets/framework/new-nav.scss
index c3badec59c9..e69de29bb2d 100644
--- a/app/assets/stylesheets/framework/new-nav.scss
+++ b/app/assets/stylesheets/framework/new-nav.scss
@@ -1,403 +0,0 @@
-@import "framework/variables";
-@import 'framework/tw_bootstrap_variables';
-@import "bootstrap/variables";
-@import "framework/mixins";
-
-.content-wrapper.page-with-new-nav {
- margin-top: $new-navbar-height;
-}
-
-header.navbar-gitlab-new {
- color: $white-light;
- border-bottom: 0;
- min-height: $new-navbar-height;
-
- .logo-text {
- line-height: initial;
-
- svg {
- width: 55px;
- height: 14px;
- margin: 0;
- fill: $white-light;
- }
- }
-
- .header-content {
- display: -webkit-flex;
- display: flex;
- padding-left: 0;
- min-height: $new-navbar-height;
-
- .title-container {
- display: -webkit-flex;
- display: flex;
- -webkit-align-items: stretch;
- align-items: stretch;
- -webkit-flex: 1 1 auto;
- flex: 1 1 auto;
- padding-top: 0;
- overflow: visible;
- }
-
- .title {
- display: -webkit-flex;
- display: flex;
- padding-right: 0;
- color: currentColor;
-
- img {
- height: 28px;
- margin-right: 8px;
- }
-
- a {
- display: -webkit-flex;
- display: flex;
- align-items: center;
- padding: 2px 8px;
- margin: 5px 2px 5px -8px;
- border-radius: $border-radius-default;
-
- svg {
- @media (min-width: $screen-sm-min) {
- margin-right: 8px;
- }
- }
- }
- }
-
- .dropdown.open {
- > a {
- border-bottom-color: $white-light;
- }
- }
-
- .dropdown-menu {
- margin-top: 4px;
- min-width: 130px;
-
- @media (max-width: $screen-xs-max) {
- left: auto;
- right: 0;
- }
- }
-
- &.menu-expanded {
- @media (max-width: $screen-xs-max) {
- .title-container,
- .header-logo, {
- display: none;
- }
- }
- }
- }
-
- .dropdown-bold-header {
- color: $gl-text-color-secondary;
- font-size: 12px;
- }
-
- .navbar-collapse {
- padding-left: 0;
- box-shadow: 0;
-
- @media (max-width: $screen-xs-max) {
- margin-left: -8px;
- margin-right: -10px;
- }
-
- .nav {
- > li:not(.hidden-xs) a {
- @media (max-width: $screen-xs-max) {
- margin-left: 0;
- min-width: 100%;
- }
- }
- }
- }
-
- .container-fluid {
- .navbar-toggle {
- min-width: 45px;
- padding: 4px $gl-padding;
- margin-right: -7px;
- font-size: 14px;
- text-align: center;
- color: currentColor;
-
- &:hover,
- &:focus,
- &.active {
- color: currentColor;
- background-color: transparent;
- }
- }
-
- .navbar-nav {
- @media (max-width: $screen-xs-max) {
- display: flex;
- padding-right: 10px;
- }
-
- li {
- .badge {
- box-shadow: none;
- font-weight: $gl-font-weight-bold;
- }
- }
- }
-
- .nav > li {
- &.header-user {
- @media (max-width: $screen-xs-max) {
- padding-left: 10px;
- }
- }
-
- > a {
- will-change: color;
- margin: 4px 2px;
- padding: 6px 8px;
- height: 32px;
-
- @media (max-width: $screen-xs-max) {
- padding: 0;
- }
-
- &.header-user-dropdown-toggle {
- margin-left: 2px;
-
- .header-user-avatar {
- margin-right: 0;
- }
- }
-
- &:hover,
- &:focus {
- text-decoration: none;
- outline: 0;
- opacity: 1;
- color: $white-light;
-
- svg {
- fill: currentColor;
- }
-
- &.header-user-dropdown-toggle {
- .header-user-avatar {
- border-color: $white-light;
- }
- }
- }
- }
-
- .header-new-dropdown-toggle {
- margin-right: 0;
- }
-
- .impersonated-user,
- .impersonated-user:hover {
- margin-right: 1px;
- background-color: $white-light;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
-
- .impersonation-btn,
- .impersonation-btn:hover {
- background-color: $white-light;
- margin-left: 0;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
-
- i {
- color: $orange-500;
- font-size: 20px;
- }
- }
-
- &.active > a,
- &.dropdown.open > a {
-
- svg {
- fill: currentColor;
- }
- }
- }
- }
-}
-
-.navbar-sub-nav {
- display: -webkit-flex;
- display: flex;
- margin: 0 0 0 6px;
-
- .dropdown-chevron {
- position: relative;
- top: -1px;
- font-size: 10px;
- }
-}
-
-.navbar-gitlab-new {
- .navbar-sub-nav,
- .navbar-nav {
- > li {
- > a:hover,
- > a:focus {
- text-decoration: none;
- outline: 0;
- color: $white-light;
-
- svg {
- fill: currentColor;
- }
- }
-
- > a {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 6px 8px;
- margin: 4px 2px;
- font-size: 12px;
- color: currentColor;
- border-radius: $border-radius-default;
- height: 32px;
- font-weight: $gl-font-weight-bold;
-
- svg {
- fill: currentColor;
- }
- }
-
- &.line-separator {
- margin: 8px;
- }
- }
- }
-}
-
-.admin-icon i {
- font-size: 18px;
-}
-
-.caret-down {
- height: 11px;
- width: 11px;
- margin-left: 4px;
- fill: currentColor;
-}
-
-.header-user .dropdown-menu-nav,
-.header-new .dropdown-menu-nav {
- margin-top: 4px;
-}
-
-.breadcrumbs {
- display: flex;
- min-height: 48px;
- color: $gl-text-color;
-}
-
-.breadcrumbs-container {
- display: -webkit-flex;
- display: flex;
- width: 100%;
- position: relative;
- align-items: center;
- border-bottom: 1px solid $border-color;
-}
-
-.breadcrumbs-links {
- -webkit-flex: 1;
- flex: 1;
- min-width: 0;
- align-self: center;
- color: $gl-text-color-secondary;
-
- @media (max-width: $screen-xs-max) {
- padding-left: 17px;
- border-left: 1px solid $gl-text-color-quaternary;
- }
-
- .avatar-tile {
- margin-right: 4px;
- border: 1px solid $border-color;
- border-radius: 50%;
- vertical-align: sub;
- }
-
- .text-expander {
- margin-left: 0;
- margin-right: 2px;
-
- > i {
- position: relative;
- top: 1px;
- }
- }
-}
-
-.breadcrumbs-list {
- display: -webkit-flex;
- display: flex;
- flex-wrap: wrap;
- margin-bottom: 0;
- line-height: 16px;
-
- > li {
- display: flex;
- align-items: center;
- position: relative;
-
- &:not(:last-child) {
- margin-right: 20px;
- }
-
- > a {
- font-size: 12px;
- color: currentColor;
- }
- }
-}
-
-.breadcrumb-item-text {
- @include str-truncated(128px);
- text-decoration: inherit;
-}
-
-.breadcrumbs-list-angle {
- position: absolute;
- right: -12px;
- top: 50%;
- color: $gl-text-color-tertiary;
- transform: translateY(-50%);
-}
-
-.breadcrumbs-extra {
- display: flex;
- flex: 0 0 auto;
- margin-left: auto;
-}
-
-.breadcrumbs-sub-title {
- margin: 0;
- font-size: 12px;
- font-weight: 600;
- line-height: 1;
-
- a {
- color: $gl-text-color;
- }
-}
-
-.btn-sign-in {
- margin-top: 3px;
- font-weight: $gl-font-weight-bold;
-
- &:hover {
- background-color: $white-light;
- }
-}
diff --git a/app/assets/stylesheets/framework/new-sidebar.scss b/app/assets/stylesheets/framework/new-sidebar.scss
index 9c404b7e542..caf4c7a40b1 100644
--- a/app/assets/stylesheets/framework/new-sidebar.scss
+++ b/app/assets/stylesheets/framework/new-sidebar.scss
@@ -24,7 +24,7 @@ $new-sidebar-collapsed-width: 50px;
// Override position: absolute
.right-sidebar {
position: fixed;
- height: calc(100% - #{$new-navbar-height});
+ height: calc(100% - #{$header-height});
}
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
@@ -56,8 +56,8 @@ $new-sidebar-collapsed-width: 50px;
color: $hover-color;
.settings-avatar {
- i {
- color: $hover-color;
+ svg {
+ fill: $hover-color;
}
}
}
@@ -76,12 +76,9 @@ $new-sidebar-collapsed-width: 50px;
.settings-avatar {
background-color: $white-light;
- i {
- font-size: 20px;
- width: 100%;
- color: $gl-text-color-secondary;
- text-align: center;
- align-self: center;
+ svg {
+ fill: $gl-text-color-secondary;
+ margin: auto;
}
}
@@ -90,7 +87,7 @@ $new-sidebar-collapsed-width: 50px;
z-index: 400;
width: $new-sidebar-width;
transition: left $sidebar-transition-duration;
- top: $new-navbar-height;
+ top: $header-height;
bottom: 0;
left: 0;
background-color: $gray-normal;
@@ -177,16 +174,16 @@ $new-sidebar-collapsed-width: 50px;
.nav-icon-container {
display: flex;
margin-right: 8px;
-
- svg {
- height: 16px;
- width: 16px;
- }
}
.fly-out-top-item {
display: none;
}
+
+ svg {
+ height: 16px;
+ width: 16px;
+ }
}
.nav-sidebar-inner-scroll {
@@ -200,7 +197,7 @@ $new-sidebar-collapsed-width: 50px;
}
.with-performance-bar .nav-sidebar {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
}
.sidebar-sub-level-items {
@@ -354,18 +351,22 @@ $new-sidebar-collapsed-width: 50px;
display: flex;
align-items: center;
- i {
- font-size: 20px;
+ svg {
+ fill: $gl-text-color-secondary;
margin-right: 8px;
}
- .fa-angle-double-right {
+ .icon-angle-double-right {
display: none;
}
&:hover {
background-color: $border-color;
color: $gl-text-color;
+
+ svg {
+ fill: $gl-text-color;
+ }
}
}
@@ -407,15 +408,16 @@ $new-sidebar-collapsed-width: 50px;
.toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px;
- padding: 16px 18px;
+ padding: 16px;
.collapse-text,
- .fa-angle-double-left {
+ .icon-angle-double-left {
display: none;
}
- .fa-angle-double-right {
+ .icon-angle-double-right {
display: block;
+ margin: 0;
}
}
}
@@ -461,6 +463,13 @@ $new-sidebar-collapsed-width: 50px;
font-size: 18px;
}
}
+
+ @media (max-width: $screen-xs-max) {
+ + .breadcrumbs-links {
+ padding-left: 17px;
+ border-left: 1px solid $gl-text-color-quaternary;
+ }
+ }
}
@media (max-width: $screen-xs-max) {
@@ -486,7 +495,7 @@ $new-sidebar-collapsed-width: 50px;
// Make issue boards full-height now that sub-nav is gone
.boards-list {
- height: calc(100vh - #{$new-navbar-height});
+ height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
@@ -497,5 +506,5 @@ $new-sidebar-collapsed-width: 50px;
}
.with-performance-bar .boards-list {
- height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height});
+ height: calc(100vh - #{$header-height} - #{$performance-bar-height});
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
index f8777d1fd9d..5c96b3b78e7 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
@@ -1,12 +1,11 @@
-
-
+// For tabbed navigation links, scrolling tabs, etc. For all top/main navigation,
+// please check nav.scss
.nav-links {
display: flex;
padding: 0;
margin: 0;
list-style: none;
height: auto;
- border-bottom: 1px solid $border-color;
li {
display: flex;
@@ -24,7 +23,6 @@
&:active,
&:focus {
text-decoration: none;
- border-bottom: 2px solid $gray-darkest;
color: $black;
.badge {
@@ -34,7 +32,6 @@
}
&.active a {
- border-bottom: 2px solid $link-underline-blue;
color: $black;
font-weight: $gl-font-weight-bold;
@@ -43,35 +40,6 @@
}
}
}
-
- &.sub-nav {
- text-align: center;
- background-color: $gray-normal;
-
- .container-fluid {
- background-color: $gray-normal;
- margin-bottom: 0;
- display: flex;
- }
-
- li {
- &.active a {
- border-bottom: none;
- color: $link-underline-blue;
- }
-
- a {
- margin: 0;
- padding: 11px 10px 9px;
-
- &:hover,
- &:active,
- &:focus {
- border-color: transparent;
- }
- }
- }
- }
}
.top-area {
@@ -91,17 +59,6 @@
}
}
- .nav-search {
- display: inline-block;
- width: 100%;
- padding: 11px 0;
-
- /* Small devices (phones, tablets, 768px and lower) */
- @media (min-width: $screen-sm-min) {
- width: 50%;
- }
- }
-
.nav-links {
margin-bottom: 0;
border-bottom: none;
@@ -150,12 +107,6 @@
}
}
- &.nav-controls-new-nav {
- > .dropdown {
- margin-right: 0;
- }
- }
-
> .btn-grouped {
float: none;
}
@@ -248,114 +199,43 @@
pre {
width: 100%;
}
-}
-
-.project-item-select-holder.btn-group {
- display: flex;
- max-width: 350px;
- overflow: hidden;
- float: right;
-
- .new-project-item-link {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .new-project-item-select-button {
- width: 32px;
- }
-}
-
-.empty-state .project-item-select-holder.btn-group {
- float: none;
- display: inline-block;
-
- .btn {
- // overrides styles applied to plain `.empty-state .btn`
- margin: 10px 0;
- max-width: 300px;
- width: auto;
-
- @media(max-width: $screen-xs-max) {
- max-width: 250px;
- }
-
- }
-}
-.new-project-item-select-button .fa-caret-down {
- margin-left: 2px;
-}
-
-.layout-nav {
- width: 100%;
- background: $gray-light;
- border-bottom: 1px solid $border-color;
- transition: padding $sidebar-transition-duration;
- text-align: center;
- margin-top: $new-navbar-height;
+ @media (max-width: $screen-xs-max) {
+ flex-flow: row wrap;
- .container-fluid {
- position: relative;
+ .nav-controls {
+ $controls-margin: $btn-xs-side-margin - 2px;
+ flex: 0 0 100%;
- .nav-control {
- @media (max-width: $screen-sm-max) {
- margin-right: 2px;
+ &.controls-flex {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+ padding: 0 0 $gl-padding-top;
}
- }
- }
-
- .controls {
- float: right;
- padding: 7px 0 0;
-
- i {
- color: $layout-link-gray;
- }
-
- .fa-rss,
- .fa-cog {
- font-size: 16px;
- }
- .fa-caret-down {
- margin-left: 5px;
- color: $gl-text-color-secondary;
- }
-
- .dropdown {
- position: absolute;
- top: 7px;
- right: 15px;
- z-index: 300;
+ .controls-item,
+ .controls-item-full,
+ .controls-item:last-child {
+ flex: 1 1 35%;
+ display: block;
+ width: 100%;
+ margin: $controls-margin;
- li.active {
- font-weight: $gl-font-weight-bold;
+ .btn,
+ .dropdown {
+ margin: 0;
+ }
}
- }
- }
- .nav-links {
- border-bottom: none;
- height: 51px;
-
- @media (min-width: $screen-sm-min) {
- justify-content: center;
- }
-
- li {
- a {
- padding-top: 10px;
+ .controls-item-full {
+ flex: 1 1 100%;
}
}
}
}
-.with-performance-bar .layout-nav {
- margin-top: $header-height + $performance-bar-height;
-}
-
.scrolling-tabs-container {
position: relative;
@@ -385,25 +265,41 @@
left: -7px;
}
}
+}
- &.sub-nav-scroll {
+.inner-page-scroll-tabs {
+ position: relative;
- .fade-right {
- @include fade(left, $gray-normal);
- right: 0;
+ .fade-right {
+ @include fade(left, $white-light);
+ right: 0;
+ text-align: right;
- .fa {
- right: -23px;
- }
+ .fa {
+ right: 5px;
}
+ }
- .fade-left {
- @include fade(right, $gray-normal);
- left: 0;
+ .fade-left {
+ @include fade(right, $white-light);
+ left: 0;
+ text-align: left;
- .fa {
- left: 10px;
- }
+ .fa {
+ left: 5px;
+ }
+ }
+
+ .fade-right,
+ .fade-left {
+ top: 16px;
+ bottom: auto;
+ }
+
+ &.is-smaller {
+ .fade-right,
+ .fade-left {
+ top: 11px;
}
}
}
@@ -432,41 +328,7 @@
}
}
}
-}
-
-.page-with-layout-nav {
- .right-sidebar {
- top: ($header-height + 1) * 2;
- }
-
- &.page-with-sub-nav {
- .right-sidebar {
- top: ($header-height + 1) * 3;
-
- &.affix {
- top: $header-height;
- }
- }
- }
-}
-
-.with-performance-bar .page-with-layout-nav {
- .right-sidebar {
- top: ($header-height + 1) * 2 + $performance-bar-height;
- }
-
- &.page-with-sub-nav {
- .right-sidebar {
- top: ($header-height + 1) * 3 + $performance-bar-height;
-
- &.affix {
- top: $header-height + $performance-bar-height;
- }
- }
- }
-}
-.nav-block {
&.activities {
border-bottom: 1px solid $border-color;
@@ -476,76 +338,39 @@
}
}
-@media (max-width: $screen-xs-max) {
- .top-area {
- flex-flow: row wrap;
-
- .nav-controls {
- $controls-margin: $btn-xs-side-margin - 2px;
- flex: 0 0 100%;
-
- &.controls-flex {
- display: flex;
- flex-flow: row wrap;
- align-items: center;
- justify-content: center;
- padding: 0 0 $gl-padding-top;
- }
-
- .controls-item,
- .controls-item-full,
- .controls-item:last-child {
- flex: 1 1 35%;
- display: block;
- width: 100%;
- margin: $controls-margin;
+.project-item-select-holder.btn-group {
+ display: flex;
+ max-width: 350px;
+ overflow: hidden;
+ float: right;
- .btn,
- .dropdown {
- margin: 0;
- }
- }
+ .new-project-item-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
- .controls-item-full {
- flex: 1 1 100%;
- }
- }
+ .new-project-item-select-button {
+ width: 32px;
}
}
-.inner-page-scroll-tabs {
- position: relative;
-
- .fade-right {
- @include fade(left, $white-light);
- right: 0;
- text-align: right;
-
- .fa {
- right: 5px;
- }
- }
+.empty-state .project-item-select-holder.btn-group {
+ float: none;
+ display: inline-block;
- .fade-left {
- @include fade(right, $white-light);
- left: 0;
- text-align: left;
+ .btn {
+ // overrides styles applied to plain `.empty-state .btn`
+ margin: 10px 0;
+ max-width: 300px;
+ width: auto;
- .fa {
- left: 5px;
+ @media(max-width: $screen-xs-max) {
+ max-width: 250px;
}
}
+}
- .fade-right,
- .fade-left {
- top: 16px;
- bottom: auto;
- }
-
- &.is-smaller {
- .fade-right,
- .fade-left {
- top: 11px;
- }
- }
+.new-project-item-select-button .fa-caret-down {
+ margin-left: 2px;
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 6c14e8b97e0..50f1445bc2e 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -48,31 +48,24 @@
}
&:hover {
- background-color: $white-normal;
- border-color: $border-white-normal;
+ border-color: $gray-darkest;
color: $gl-text-color;
}
}
}
-.select2-drop {
- box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0;
- border-radius: $border-radius-default;
- border: none;
+.select2-drop,
+.select2-drop.select2-drop-above {
+ box-shadow: 0 2px 4px $dropdown-shadow-color;
+ border-radius: $border-radius-base;
+ border: 1px solid $dropdown-border-color;
min-width: 175px;
+ color: $gl-text-color;
}
-.select2-results .select2-result-label,
-.select2-more-results {
- padding: 10px 15px;
-}
-
-.select2-drop {
- color: $gl-grayish-blue;
-}
-
-.select2-highlighted {
- background: $gl-link-color !important;
+.select2-drop.select2-drop-above.select2-drop-active {
+ border-top: 1px solid $dropdown-border-color;
+ margin-top: -6px;
}
.select2-results li.select2-result-with-children > .select2-result-label {
@@ -87,13 +80,11 @@
}
}
-.select2-dropdown-open {
+.select2-dropdown-open,
+.select2-dropdown-open.select2-drop-above {
.select2-choice {
- border-color: $border-white-normal;
+ border-color: $gray-darkest;
outline: 0;
- background-image: none;
- background-color: $white-dark;
- box-shadow: $gl-btn-active-gradient;
}
}
@@ -131,28 +122,14 @@
}
}
}
-
- &.select2-container-active .select2-choices,
- &.select2-dropdown-open .select2-choices {
- border-color: $border-white-normal;
- box-shadow: $gl-btn-active-gradient;
- }
}
.select2-drop-active {
- margin-top: 6px;
+ margin-top: $dropdown-vertical-offset;
font-size: 14px;
- &.select2-drop-above {
- margin-bottom: 8px;
- }
-
.select2-results {
max-height: 350px;
-
- .select2-highlighted {
- background: $gl-primary;
- }
}
}
@@ -186,19 +163,35 @@
background-size: 16px 16px !important;
}
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-ajax-error,
-.select2-results .select2-selection-limit {
- background: $gray-light;
- display: list-item;
- padding: 10px 15px;
-}
-
-
.select2-results {
margin: 0;
- padding: 10px 0;
+ padding: #{$gl-padding / 2} 0;
+
+ .select2-no-results,
+ .select2-searching,
+ .select2-ajax-error,
+ .select2-selection-limit {
+ background: transparent;
+ padding: #{$gl-padding / 2} $gl-padding;
+ }
+
+ .select2-result-label,
+ .select2-more-results {
+ padding: #{$gl-padding / 2} $gl-padding;
+ }
+
+ .select2-highlighted {
+ background: transparent;
+ color: $gl-text-color;
+
+ .select2-result-label {
+ background: $dropdown-item-hover-bg;
+ }
+ }
+
+ .select2-result {
+ padding: 0 1px;
+ }
}
.ajax-users-select {
@@ -265,56 +258,10 @@
min-width: 250px !important;
}
-// TODO: change global style
-.ajax-project-dropdown,
-.ajax-users-dropdown,
-body[data-page="projects:edit"] #select2-drop,
-body[data-page="projects:new"] #select2-drop,
-body[data-page="projects:merge_requests:edit"] #select2-drop,
-body[data-page="projects:blob:new"] #select2-drop,
-body[data-page="profiles:show"] #select2-drop,
-body[data-page="admin:groups:show"] #select2-drop,
-body[data-page="projects:issues:show"] #select2-drop,
-body[data-page="projects:blob:edit"] #select2-drop {
- &.select2-drop {
- border: 1px solid $dropdown-border-color;
- border-radius: $border-radius-base;
- color: $gl-text-color;
- }
-
- &.select2-drop-above {
- border-top: none;
- margin-top: -4px;
- }
-
- .select2-results {
- .select2-no-results,
- .select2-searching,
- .select2-ajax-error,
- .select2-selection-limit {
- background: transparent;
- }
-
- .select2-result {
- padding: 0 1px;
-
- .select2-match {
- font-weight: $gl-font-weight-bold;
- text-decoration: none;
- }
-
- .select2-result-label {
- padding: #{$gl-padding / 2} $gl-padding;
- }
-
- &.select2-highlighted {
- background-color: transparent !important;
- color: $gl-text-color;
-
- .select2-result-label {
- background-color: $dropdown-item-hover-bg;
- }
- }
- }
+.select2-result-selectable,
+.select2-result-unselectable {
+ .select2-match {
+ font-weight: $gl-font-weight-bold;
+ text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 48dc25d343b..ef58382ba41 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -78,16 +78,16 @@
.right-sidebar {
border-left: 1px solid $border-color;
- height: calc(100% - #{$new-navbar-height});
+ height: calc(100% - #{$header-height});
&.affix {
position: fixed;
- top: $new-navbar-height;
+ top: $header-height;
}
}
.with-performance-bar .right-sidebar.affix {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
}
@mixin maintain-sidebar-dimensions {
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 3d68a50f91f..f718ec4bcad 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -17,15 +17,19 @@
.diff-file {
border: 1px solid $border-color;
- border-bottom: none;
margin: 0;
}
+
+ &.text-file .diff-file {
+ border-bottom: none;
+ }
}
.timeline-entry {
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
+ background: $white-light;
.timeline-entry-inner {
position: relative;
diff --git a/app/assets/stylesheets/framework/tooltips.scss b/app/assets/stylesheets/framework/tooltips.scss
new file mode 100644
index 00000000000..93baf73cb78
--- /dev/null
+++ b/app/assets/stylesheets/framework/tooltips.scss
@@ -0,0 +1,7 @@
+.tooltip-inner {
+ font-size: $tooltip-font-size;
+ border-radius: $border-radius-default;
+ line-height: 16px;
+ font-weight: $gl-font-weight-normal;
+ padding: $gl-btn-padding;
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ca514add9cd..bbbd16322eb 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,12 +1,14 @@
/*
* Layout
*/
+$grid-size: 8px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 250px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px;
$default-transition-duration: .15s;
+$right-sidebar-transition-duration: .3s;
/*
* Color schema
@@ -202,6 +204,11 @@ $code_font_size: 12px;
$code_line_height: 1.6;
/*
+ * Tooltips
+ */
+$tooltip-font-size: 12px;
+
+/*
* Padding
*/
$gl-padding: 16px;
@@ -218,8 +225,7 @@ $gl-sidebar-padding: 22px;
$row-hover: $blue-50;
$row-hover-border: $blue-200;
$progress-color: #c0392b;
-$header-height: 50px;
-$new-navbar-height: 40px;
+$header-height: 40px;
$fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$limited-layout-width-sm: 790px;
@@ -261,7 +267,8 @@ $well-pre-bg: #eee;
$well-pre-color: #555;
$loading-color: #555;
$update-author-color: #999;
-$user-mention-color: #2fa0bb;
+$user-mention-bg: rgba($blue-500, 0.044);
+$user-mention-bg-hover: rgba($blue-500, 0.15);
$time-color: #999;
$project-member-show-color: #aaa;
$gl-promo-color: #aaa;
@@ -315,6 +322,7 @@ $diff-image-info-color: grey;
$diff-swipe-border: #999;
$diff-view-modes-color: grey;
$diff-view-modes-border: #c1c1c1;
+$diff-jagged-border-gradient-color: darken($white-normal, 8%);
/*
* Fonts
@@ -326,6 +334,7 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San
* Dropdowns
*/
$dropdown-width: 300px;
+$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04);
@@ -697,3 +706,15 @@ Project Templates Icons
$rails: #c00;
$node: #353535;
$java: #70ad51;
+
+/*
+Issuable warning
+*/
+$issuable-warning-size: 24px;
+$issuable-warning-icon-margin: 4px;
+
+/*
+Image Commenting cursor
+*/
+$image-comment-cursor-left-offset: 12;
+$image-comment-cursor-top-offset: 30;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 700be173039..ca61f7a30c3 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -55,6 +55,15 @@
.boards-app {
position: relative;
+
+ @media (min-width: $screen-sm-min) {
+ transition: width $right-sidebar-transition-duration;
+ width: 100%;
+
+ &.is-compact {
+ width: calc(100% - #{$gutter_width});
+ }
+ }
}
.boards-app-loading {
@@ -78,11 +87,6 @@
height: calc(100vh - 222px);
// scss-lint:enable DuplicateProperty
min-height: 475px;
- transition: width .2s;
-
- &.is-compact {
- width: calc(100% - 290px);
- }
}
}
@@ -410,16 +414,7 @@
margin: 5px;
}
-.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar,
.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
- position: absolute;
-
- &.right-sidebar {
- top: 0;
- bottom: 0;
- height: 100%;
- }
-
.issuable-sidebar-header {
position: relative;
}
@@ -457,8 +452,8 @@
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
- transition: width .2s,
- padding .2s;
+ transition: width $right-sidebar-transition-duration,
+ padding $right-sidebar-transition-duration;
}
&.boards-sidebar-slide-enter,
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 359dd388d05..50ec5110bf1 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -64,10 +64,10 @@
color: $gl-text-color;
position: sticky;
position: -webkit-sticky;
- top: $new-navbar-height;
+ top: $header-height;
&.affix {
- top: $new-navbar-height;
+ top: $header-height;
}
// with sidebar
@@ -174,10 +174,10 @@
.with-performance-bar .build-page {
.top-bar {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
&.affix {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
}
}
}
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
new file mode 100644
index 00000000000..5538e46a6c4
--- /dev/null
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -0,0 +1,9 @@
+.edit-cluster-form {
+ .clipboard-addon {
+ background-color: $white-light;
+ }
+
+ .alert-block {
+ margin-bottom: 20px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
index 3266714396e..dfff3e15556 100644
--- a/app/assets/stylesheets/pages/container_registry.scss
+++ b/app/assets/stylesheets/pages/container_registry.scss
@@ -9,6 +9,14 @@
.container-image-head {
padding: 0 16px;
line-height: 4em;
+
+ .btn-link {
+ padding: 0;
+
+ &:focus {
+ outline: none;
+ }
+ }
}
.table.tags {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index ed9d5e98467..ffb5fc94475 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -77,6 +77,18 @@
word-wrap: break-word;
}
}
+
+ &.left-side-selected {
+ td.line_content.parallel.right-side {
+ @include user-select(none);
+ }
+ }
+
+ &.right-side-selected {
+ td.line_content.parallel.left-side {
+ @include user-select(none);
+ }
+ }
}
tr.line_holder.parallel {
@@ -285,6 +297,7 @@
.drag-track {
display: block;
position: absolute;
+ top: 0;
left: 12px;
height: 10px;
width: 276px;
@@ -535,21 +548,23 @@
}
.diff-notes-collapse {
- position: relative;
- width: 19px;
- height: 19px;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
padding: 0;
transition: transform .1s ease-out;
z-index: 100;
+ .collapse-icon {
+ height: 50%;
+ width: 100%;
+ }
+
svg {
- position: absolute;
- left: 50%;
- top: 50%;
- margin-left: -5.5px;
- margin-top: -5.5px;
+ vertical-align: middle;
}
+ .collapse-icon,
path {
fill: $white-light;
}
@@ -637,3 +652,157 @@
text-overflow: ellipsis;
white-space: nowrap;
}
+
+.note-container {
+ background-color: $gray-light;
+ border-top: 1px solid $white-normal;
+
+ // double jagged line divider
+ .discussion-notes + .discussion-notes::before,
+ .discussion-notes + .discussion-form::before {
+ content: '';
+ position: relative;
+ display: block;
+ width: 100%;
+ height: 10px;
+ background-color: $white-light;
+ background-image: linear-gradient(45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
+ linear-gradient(225deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
+ linear-gradient(135deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
+ linear-gradient(-45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%);
+ background-position: 5px 5px,0 5px,0 5px,5px 5px;
+ background-size: 10px 10px;
+ background-repeat: repeat;
+ }
+
+ .notes {
+ position: relative;
+ }
+
+ .diff-notes-collapse {
+ position: absolute;
+ left: -12px;
+ }
+}
+
+.diff-file .note-container > .new-note,
+.note-container .discussion-notes {
+ margin-left: 100px;
+ border-left: 1px solid $white-normal;
+}
+
+.notes.active {
+ .diff-file .note-container > .new-note,
+ .note-container .discussion-notes {
+ // Override our margin and border (set for diff tab)
+ // when user is on the discussion tab for MR
+ margin-left: inherit;
+ border-left: inherit;
+ }
+}
+
+.files:not([data-can-create-note]) .frame {
+ cursor: auto;
+}
+
+.frame.click-to-comment {
+ position: relative;
+ cursor: url(icon_image_comment.svg)
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
+
+ // Retina cursor
+ cursor: -webkit-image-set(url(icon_image_comment.svg) 1x, url(icon_image_comment@2x.svg) 2x)
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
+
+ .comment-indicator {
+ position: absolute;
+ padding: 0;
+ width: (2px * $image-comment-cursor-left-offset);
+ height: (1px * $image-comment-cursor-top-offset);
+ // center the indicator to match the top left click region
+ margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
+ margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+
+ &:focus {
+ outline: none;
+ }
+ }
+}
+
+.frame .badge,
+.image-diff-avatar-link .badge,
+.notes > .badge {
+ position: absolute;
+ background-color: $blue-400;
+ color: $white-light;
+ border: $white-light 1px solid;
+ min-height: $gl-padding;
+ padding: 5px 8px;
+ border-radius: 12px;
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.frame .badge,
+.frame .image-comment-badge {
+ // Center align badges on the frame
+ transform: translate3d(-50%, -50%, 0);
+}
+
+.image-comment-badge {
+ @include btn-comment-icon;
+ position: absolute;
+
+ &.inverted {
+ border-color: $white-light;
+ }
+}
+
+.image-diff-avatar-link {
+ position: relative;
+
+ .badge,
+ .image-comment-badge {
+ top: 25px;
+ right: 8px;
+ }
+}
+
+.notes > .badge {
+ display: none;
+ left: -13px;
+}
+
+.discussion-notes {
+ min-height: 35px;
+
+ &:first-child {
+ // First child does not have the jagged borders
+ min-height: 25px;
+ }
+
+ &.collapsed {
+ background-color: $white-light;
+
+ .diff-notes-collapse,
+ .note,
+ .discussion-reply-holder, {
+ display: none;
+ }
+
+ .notes > .badge {
+ display: block;
+ }
+ }
+}
+
+.discussion-body .image .frame {
+ position: relative;
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 9362d80d4e6..3b5e411e2c5 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -207,10 +207,13 @@
}
.prometheus-state {
- margin-top: 10px;
+ max-width: 430px;
+ margin: 10px auto;
+ text-align: center;
- .state-button-section {
- margin-top: 10px;
+ .state-svg {
+ max-width: 80vw;
+ margin: 0 auto;
}
}
@@ -288,8 +291,14 @@
fill: $black;
}
- .tick > text {
- font-size: 12px;
+ .tick {
+ > line {
+ stroke: $gray-darker;
+ }
+
+ > text {
+ font-size: 12px;
+ }
}
.text-metric-title {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 3ddf3d8ea7a..dae3ec7ac42 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -5,27 +5,29 @@
margin-right: auto;
}
-.is-confidential {
+.issuable-warning-icon {
color: $orange-600;
background-color: $orange-100;
border-radius: $border-radius-default;
padding: 5px;
- margin: 0 3px 0 -4px;
+ margin: 0 $btn-side-margin 0 0;
+ width: $issuable-warning-size;
+ height: $issuable-warning-size;
+ text-align: center;
+
+ &:first-of-type {
+ margin-right: $issuable-warning-icon-margin;
+ }
}
-.is-not-confidential {
+.sidebar-item-icon {
border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
-}
-.confidentiality {
- .is-not-confidential {
- margin: auto;
- }
-
- .is-confidential {
- margin: auto;
+ &.is-active {
+ color: $orange-600;
+ background-color: $orange-50;
}
}
@@ -220,17 +222,17 @@
.right-sidebar {
position: absolute;
- top: $new-navbar-height;
+ top: $header-height;
bottom: 0;
right: 0;
- transition: width .3s;
+ transition: width $right-sidebar-transition-duration;
background: $gray-light;
z-index: 200;
overflow: hidden;
.issuable-sidebar {
width: calc(100% + 100px);
- height: calc(100% - #{$new-navbar-height});
+ height: 100%;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
@@ -485,10 +487,10 @@
}
.with-performance-bar .right-sidebar {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
.issuable-sidebar {
- height: calc(100% - #{$new-navbar-height} - #{$performance-bar-height});
+ height: calc(100% - #{$header-height} - #{$performance-bar-height});
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 09a14578dd3..d9fb3b44d29 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -649,7 +649,7 @@
}
.merge-request-tabs-holder {
- top: $new-navbar-height;
+ top: $header-height;
z-index: 200;
background-color: $white-light;
border-bottom: 1px solid $border-color;
@@ -679,7 +679,7 @@
}
.with-performance-bar .merge-request-tabs-holder {
- top: $new-navbar-height + $performance-bar-height;
+ top: $header-height + $performance-bar-height;
}
.merge-request-tabs {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index be4db597689..04b132415eb 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -101,7 +101,7 @@
}
}
-.confidential-issue-warning {
+.issuable-note-warning {
color: $orange-600;
background-color: $orange-100;
border-radius: $border-radius-default $border-radius-default 0 0;
@@ -110,37 +110,64 @@
padding: 3px 12px;
margin: auto;
align-items: center;
+
+ .icon {
+ margin-right: $issuable-warning-icon-margin;
+ }
+}
+
+.disabled-comment .issuable-note-warning {
+ border: none;
+ border-radius: $label-border-radius;
+ padding-top: $gl-vert-padding;
+ padding-bottom: $gl-vert-padding;
+
+ .icon svg {
+ position: relative;
+ top: 2px;
+ margin-right: $btn-xs-side-margin;
+ width: $gl-font-size;
+ height: $gl-font-size;
+ fill: $orange-600;
+ }
}
-.confidential-value {
+.sidebar-item-value {
.fa {
background-color: inherit;
}
}
-.confidential-warning-message {
+.sidebar-item-warning-message {
line-height: 1.5;
padding: 16px;
- .confidential-warning-message-actions {
+ .text {
+ color: $text-color;
+ }
+
+ .sidebar-item-warning-message-actions {
display: flex;
- button {
+ .btn {
flex-grow: 1;
}
}
}
-.confidential-issue-warning + .md-area {
+.issuable-note-warning + .md-area {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.discussion-form {
- padding: $gl-padding-top $gl-padding $gl-padding;
background-color: $white-light;
}
+.discussion-form-container {
+ padding: $gl-padding-top $gl-padding $gl-padding;
+}
+
.discussion-notes .disabled-comment {
padding: 6px 0;
}
@@ -362,7 +389,7 @@
.dropdown-menu {
top: initial;
- bottom: 40px;
+ bottom: 100%;
width: 298px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 46d31e41ada..96b7db3b85d 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -650,29 +650,12 @@ ul.notes {
}
.add-diff-note {
+ @include btn-comment-icon;
opacity: 0;
margin-top: -2px;
- border-radius: 50%;
- background: $white-light;
- padding: 1px 5px;
- font-size: 12px;
- color: $blue-500;
margin-left: -55px;
position: absolute;
z-index: 10;
- width: 23px;
- height: 23px;
- border: 1px solid $blue-500;
-
- &:hover {
- background: $blue-500;
- border-color: $blue-600;
- color: $white-light;
- }
-
- &:active {
- outline: 0;
- }
}
.discussion-body,
@@ -703,6 +686,12 @@ ul.notes {
color: $note-disabled-comment-color;
padding: 90px 0;
+ &.discussion-locked {
+ border: none;
+ background-color: $white-light;
+ }
+
+
a {
color: $gl-link-color;
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 67abe6e88ed..eab39f698c3 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -108,6 +108,15 @@
}
}
+.subkeys-list {
+ @include basic-list;
+
+ li {
+ padding: 3px 0;
+ border: none;
+ }
+}
+
.key-list-item {
.key-list-item-info {
@media (min-width: $screen-sm-min) {
@@ -392,11 +401,11 @@ table.u2f-registrations {
}
}
-.gpg-email-badge {
+.email-badge {
display: inline;
margin-right: $gl-padding / 2;
- .gpg-email-badge-email {
+ .email-badge-email {
display: inline;
margin-right: $gl-padding / 4;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6400b72742c..a086c11324d 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -499,64 +499,56 @@ a.deploy-project-label {
}
}
-.fork-namespaces {
- .row {
- -webkit-flex-wrap: wrap;
- display: -webkit-flex;
- display: flex;
- flex-wrap: wrap;
- justify-content: flex-start;
+.fork-thumbnail {
+ height: 200px;
+ width: calc((100% / 2) - #{$gl-padding * 2});
- .fork-thumbnail {
- border-radius: $border-radius-base;
- background-color: $white-light;
- border: 1px solid $border-white-light;
- height: 202px;
- margin: $gl-padding;
- text-align: center;
- width: 169px;
+ @media (min-width: $screen-md-min) {
+ width: calc((100% / 4) - #{$gl-padding * 2});
+ }
- &:hover,
- &.forked {
- background-color: $row-hover;
- border-color: $row-hover-border;
- }
+ @media (min-width: $screen-lg-min) {
+ width: calc((100% / 5) - #{$gl-padding * 2});
+ }
- .no-avatar {
- width: 100px;
- height: 100px;
- background-color: $gray-light;
- border: 1px solid $white-normal;
- margin: 0 auto;
- border-radius: 50%;
-
- i {
- font-size: 100px;
- color: $white-normal;
- }
- }
+ &:hover:not(.disabled),
+ &.forked {
+ background-color: $row-hover;
+ border-color: $row-hover-border;
+ }
- a {
- display: block;
- width: 100%;
- height: 100%;
- padding-top: $gl-padding;
- color: $gl-text-color;
-
- .caption {
- min-height: 30px;
- padding: $gl-padding 0;
- }
- }
+ .avatar-container,
+ .identicon {
+ float: none;
+ margin-left: auto;
+ margin-right: auto;
+ }
- img {
- border-radius: 50%;
- max-width: 100px;
- }
+ a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ padding-top: $gl-padding;
+ text-decoration: none;
+
+ &.disabled {
+ opacity: .3;
+ cursor: not-allowed;
}
}
}
+.fork-thumbnail-container {
+ display: flex;
+ flex-wrap: wrap;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+
+ > h5 {
+ width: 100%;
+ }
+}
+
.project-template,
.project-import {
.form-group {
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 4d4d92f9494..c36fe25f74d 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -54,6 +54,10 @@
border-radius: $border-radius-default;
color: $almost-black;
+ .code.white pre .hll {
+ background-color: $well-light-border !important;
+ }
+
.tree-content-holder {
display: flex;
min-height: 300px;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 2fa710a05b5..db0a04a5eb3 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -47,6 +47,7 @@ input[type="checkbox"]:hover {
}
.location-badge {
+ height: 32px;
font-size: 12px;
margin: -4px 4px -4px -4px;
line-height: 25px;
@@ -81,17 +82,10 @@ input[type="checkbox"]:hover {
.clear-icon {
position: absolute;
right: 5px;
- top: 0;
-
- &::before {
- font-family: FontAwesome;
- font-weight: $gl-font-weight-normal;
- font-style: normal;
- }
+ top: 4px;
}
.search-icon {
- @extend .fa-search;
transition: color $default-transition-duration;
-webkit-user-select: none;
-moz-user-select: none;
@@ -99,7 +93,6 @@ input[type="checkbox"]:hover {
}
.clear-icon {
- @extend .fa-times;
display: none;
}
diff --git a/app/assets/stylesheets/pages/settings_ci_cd.scss b/app/assets/stylesheets/pages/settings_ci_cd.scss
index fe22d186af1..a355e2dee24 100644
--- a/app/assets/stylesheets/pages/settings_ci_cd.scss
+++ b/app/assets/stylesheets/pages/settings_ci_cd.scss
@@ -12,3 +12,7 @@
margin-left: 10px;
}
}
+
+.registry-placeholder {
+ min-height: 60px;
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 224eee90a3f..e2f6e511c86 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -169,6 +169,14 @@
}
}
+ .tree-item-file-external-link {
+ margin-right: 4px;
+
+ span {
+ text-decoration: inherit;
+ }
+ }
+
.tree_commit {
max-width: 320px;
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index a4648b33cfa..c27f2ee3c09 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -3,9 +3,23 @@
# Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController
before_action :authenticate_admin!
+ before_action :display_read_only_information
layout 'admin'
def authenticate_admin!
render_404 unless current_user.admin?
end
+
+ def display_read_only_information
+ return unless Gitlab::Database.read_only?
+
+ flash.now[:notice] = read_only_message
+ end
+
+ private
+
+ # Overridden in EE
+ def read_only_message
+ _('You are on a read-only GitLab instance.')
+ end
end
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 16590e66d61..fb6d8c0bb81 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -22,8 +22,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
@application = Doorkeeper::Application.new(application_params)
if @application.save
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to admin_application_url(@application)
+ redirect_to_admin_page
else
render :new
end
@@ -42,6 +41,13 @@ class Admin::ApplicationsController < Admin::ApplicationController
redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.'
end
+ protected
+
+ def redirect_to_admin_page
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to admin_application_url(@application)
+ end
+
private
def set_application
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index cbcef70e957..156a8e2c515 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -128,7 +128,7 @@ class Admin::UsersController < Admin::ApplicationController
end
respond_to do |format|
- result = Users::UpdateService.new(user, user_params_with_pass).execute do |user|
+ result = Users::UpdateService.new(current_user, user_params_with_pass.merge(user: user)).execute do |user|
user.skip_reconfirmation!
end
@@ -155,7 +155,7 @@ class Admin::UsersController < Admin::ApplicationController
def remove_email
email = user.emails.find(params[:email_id])
- success = Emails::DestroyService.new(user, email: email.email).execute
+ success = Emails::DestroyService.new(current_user, user: user).execute(email)
respond_to do |format|
if success
@@ -219,7 +219,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def update_user(&block)
- result = Users::UpdateService.new(user).execute(&block)
+ result = Users::UpdateService.new(current_user, user: user).execute(&block)
result[:status] == :success
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 97922e39ba8..967fe39256a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -25,6 +25,8 @@ class ApplicationController < ActionController::Base
around_action :set_locale
+ after_action :set_page_title_header, if: -> { request.format == :json }
+
protect_from_forgery with: :exception
helper_method :can?, :current_application_settings
@@ -83,12 +85,21 @@ class ApplicationController < ActionController::Base
super
payload[:remote_ip] = request.remote_ip
- if current_user.present?
- payload[:user_id] = current_user.id
- payload[:username] = current_user.username
+ logged_user = auth_user
+
+ if logged_user.present?
+ payload[:user_id] = logged_user.try(:id)
+ payload[:username] = logged_user.try(:username)
end
end
+ # Controllers such as GitHttpController may use alternative methods
+ # (e.g. tokens) to authenticate the user, whereas Devise sets current_user
+ def auth_user
+ return current_user if current_user.present?
+ return try(:authenticated_user)
+ end
+
# This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
@@ -335,4 +346,9 @@ class ApplicationController < ActionController::Base
sign_in user, store: false
end
end
+
+ def set_page_title_header
+ # Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
+ response.headers['Page-Title'] = page_title('GitLab').encode('ISO-8859-1')
+ end
end
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 0d74078645a..737656b3dcc 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -10,7 +10,7 @@ module Boards
def index
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20)
- make_sure_position_is_set(issues)
+ make_sure_position_is_set(issues) if Gitlab::Database.read_write?
issues = issues.preload(:project,
:milestone,
:assignees,
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index b75e401a8df..db8c362f125 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -59,6 +59,7 @@ module AuthenticatesWithTwoFactor
sign_in(user)
else
user.increment_failed_attempts!
+ Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
flash.now[:alert] = 'Invalid two-factor code.'
prompt_for_two_factor(user)
end
@@ -75,6 +76,7 @@ module AuthenticatesWithTwoFactor
sign_in(user)
else
user.increment_failed_attempts!
+ Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
flash.now[:alert] = 'Authentication via U2F device failed.'
prompt_for_two_factor(user)
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 8921d55c3d0..3181f517087 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -106,7 +106,7 @@ module IssuableCollections
# @filter_params[:authorized_only] = true
end
- @filter_params
+ @filter_params.permit(IssuableFinder::VALID_PARAMS)
end
def set_default_state
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 18fd8eb114d..1126f706393 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -15,9 +15,9 @@ module NotesActions
notes = notes_finder.execute
.inc_relations_for_view
- .reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = prepare_notes_for_rendering(notes)
+ notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes_json[:notes] =
if noteable.discussions_rendered_on_frontend?
@@ -96,7 +96,8 @@ module NotesActions
id: note.id,
discussion_id: note.discussion_id(noteable),
html: note_html(note),
- note: note.note
+ note: note.note,
+ on_image: note.try(:on_image?)
)
discussion = note.to_discussion(noteable)
@@ -122,7 +123,9 @@ module NotesActions
def diff_discussion_html(discussion)
return unless discussion.diff_discussion?
- if params[:view] == 'parallel'
+ on_image = discussion.on_image?
+
+ if params[:view] == 'parallel' && !on_image
template = "discussions/_parallel_diff_discussion"
locals =
if params[:line_type] == 'old'
@@ -132,7 +135,9 @@ module NotesActions
end
else
template = "discussions/_diff_discussion"
- locals = { discussions: [discussion] }
+ @fresh_discussion = true
+
+ locals = { discussions: [discussion], on_image: on_image }
end
render_to_string(
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 306afb65f10..80ab681ed87 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -10,12 +10,18 @@ class ConfirmationsController < Devise::ConfirmationsController
users_almost_there_path
end
- def after_confirmation_path_for(resource_name, resource)
- if signed_in?(resource_name)
- after_sign_in_path_for(resource)
+ def after_confirmation_path_for(_resource_name, resource)
+ # incoming resource can either be a :user or an :email
+ if signed_in?(:user)
+ after_sign_in(resource)
else
+ Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
flash[:notice] += " Please sign in."
- new_session_path(resource_name)
+ new_session_path(:user)
end
end
+
+ def after_sign_in(resource)
+ after_sign_in_path_for(resource)
+ end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index a8b2b93b458..02c5857eea7 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -7,9 +7,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
- if @todos.out_of_range? && @todos.total_pages != 0
- redirect_to url_for(params.merge(page: @todos.total_pages, only_path: true))
- end
+
+ return if redirect_out_of_range(@todos)
end
def destroy
@@ -60,7 +59,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end
def find_todos
- @todos ||= TodosFinder.new(current_user, params).execute
+ @todos ||= TodosFinder.new(current_user, todo_params).execute
end
def todos_counts
@@ -69,4 +68,27 @@ class Dashboard::TodosController < Dashboard::ApplicationController
done_count: number_with_delimiter(current_user.todos_done_count)
}
end
+
+ def todo_params
+ params.permit(:action_id, :author_id, :project_id, :type, :sort, :state)
+ end
+
+ def redirect_out_of_range(todos)
+ total_pages =
+ if todo_params.except(:sort, :page).empty?
+ (current_user.todos_pending_count / todos.limit_value).ceil
+ else
+ todos.total_pages
+ end
+
+ return false if total_pages.zero?
+
+ out_of_range = todos.current_page > total_pages
+
+ if out_of_range
+ redirect_to url_for(params.merge(page: total_pages, only_path: true))
+ end
+
+ out_of_range
+ end
end
diff --git a/app/controllers/google_api/authorizations_controller.rb b/app/controllers/google_api/authorizations_controller.rb
new file mode 100644
index 00000000000..5551057ff55
--- /dev/null
+++ b/app/controllers/google_api/authorizations_controller.rb
@@ -0,0 +1,29 @@
+module GoogleApi
+ class AuthorizationsController < ApplicationController
+ def callback
+ token, expires_at = GoogleApi::CloudPlatform::Client
+ .new(nil, callback_google_api_auth_url)
+ .get_token(params[:code])
+
+ session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token
+ session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
+ expires_at.to_s
+
+ state_redirect_uri = redirect_uri_from_session_key(params[:state])
+
+ if state_redirect_uri
+ redirect_to state_redirect_uri
+ else
+ redirect_to root_path
+ end
+ end
+
+ private
+
+ def redirect_uri_from_session_key(state)
+ key = GoogleApi::CloudPlatform::Client
+ .session_key_for_redirect_uri(params[:state])
+ session[key] if key
+ end
+ end
+end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 2ae4785b12c..b02e64a132b 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -21,14 +21,20 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
@application.owner = current_user
if @application.save
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to oauth_application_url(@application)
+ redirect_to_oauth_application_page
else
set_index_vars
render :index
end
end
+ protected
+
+ def redirect_to_oauth_application_page
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to oauth_application_url(@application)
+ end
+
private
def verify_user_oauth_applications_enabled
diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb
index 408650aac54..39b9f8a84d1 100644
--- a/app/controllers/profiles/avatars_controller.rb
+++ b/app/controllers/profiles/avatars_controller.rb
@@ -2,7 +2,7 @@ class Profiles::AvatarsController < Profiles::ApplicationController
def destroy
@user = current_user
- Users::UpdateService.new(@user).execute { |user| user.remove_avatar! }
+ Users::UpdateService.new(current_user, user: @user).execute { |user| user.remove_avatar! }
redirect_to profile_path, status: 302
end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index ddb67d1c4d1..bbd7ba49d77 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -1,15 +1,14 @@
class Profiles::EmailsController < Profiles::ApplicationController
+ before_action :find_email, only: [:destroy, :resend_confirmation_instructions]
+
def index
- @primary = current_user.email
+ @primary_email = current_user.email
@emails = current_user.emails.order_id_desc
end
def create
- @email = Emails::CreateService.new(current_user, email_params).execute
-
- if @email.errors.blank?
- NotificationService.new.new_email(@email)
- else
+ @email = Emails::CreateService.new(current_user, email_params.merge(user: current_user)).execute
+ unless @email.errors.blank?
flash[:alert] = @email.errors.full_messages.first
end
@@ -17,9 +16,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
end
def destroy
- @email = current_user.emails.find(params[:id])
-
- Emails::DestroyService.new(current_user, email: @email.email).execute
+ Emails::DestroyService.new(current_user, user: current_user).execute(@email)
respond_to do |format|
format.html { redirect_to profile_emails_url, status: 302 }
@@ -27,9 +24,23 @@ class Profiles::EmailsController < Profiles::ApplicationController
end
end
+ def resend_confirmation_instructions
+ if Emails::ConfirmService.new(current_user, user: current_user).execute(@email)
+ flash[:notice] = "Confirmation email sent to #{@email.email}"
+ else
+ flash[:alert] = "There was a problem sending the confirmation email"
+ end
+
+ redirect_to profile_emails_url
+ end
+
private
def email_params
params.require(:email).permit(:email)
end
+
+ def find_email
+ @email = current_user.emails.find(params[:id])
+ end
end
diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb
index 689c76059f6..38e3eacd229 100644
--- a/app/controllers/profiles/gpg_keys_controller.rb
+++ b/app/controllers/profiles/gpg_keys_controller.rb
@@ -2,7 +2,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
before_action :set_gpg_key, only: [:destroy, :revoke]
def index
- @gpg_keys = current_user.gpg_keys
+ @gpg_keys = current_user.gpg_keys.with_subkeys
@gpg_key = GpgKey.new
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 89d6d7f1b52..069e6a810f2 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -14,7 +14,7 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = Keys::CreateService.new(current_user, key_params).execute
if @key.persisted?
- redirect_to profile_key_path(@key)
+ redirect_to_profile_key_path
else
@keys = current_user.keys.select(&:persisted?)
render :index
@@ -50,6 +50,12 @@ class Profiles::KeysController < Profiles::ApplicationController
end
end
+ protected
+
+ def redirect_to_profile_key_path
+ redirect_to profile_key_path(@key)
+ end
+
private
def key_params
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 960b7512602..8a38ba65d4c 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -7,7 +7,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def update
- result = Users::UpdateService.new(current_user, user_params).execute
+ result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
if result[:status] == :success
flash[:notice] = "Notification settings saved"
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 7beb52dd8e8..dcfcb855ab5 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -21,10 +21,10 @@ class Profiles::PasswordsController < Profiles::ApplicationController
password_automatically_set: false
}
- result = Users::UpdateService.new(@user, password_attributes).execute
+ result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
- Users::UpdateService.new(@user, password_expires_at: nil).execute
+ Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute
redirect_to root_path, notice: 'Password successfully changed'
else
@@ -46,7 +46,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
return
end
- result = Users::UpdateService.new(@user, password_attributes).execute
+ result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
flash[:notice] = "Password was successfully updated. Please login with it"
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index c1cc509a748..4146deefa89 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -1,6 +1,7 @@
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def index
set_index_vars
+ @personal_access_token = finder.build
end
def create
@@ -40,7 +41,6 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def set_index_vars
@scopes = Gitlab::Auth.available_scopes
- @personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
end
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index cce2a847b53..ed0f98179eb 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -6,7 +6,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
def update
begin
- result = Users::UpdateService.new(user, preferences_params).execute
+ result = Users::UpdateService.new(current_user, preferences_params.merge(user: user)).execute
if result[:status] == :success
flash[:notice] = 'Preferences saved.'
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 1a4f77639e7..aa9789f8a0f 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.otp_grace_period_started_at = Time.current
end
- Users::UpdateService.new(current_user).execute!
+ Users::UpdateService.new(current_user, user: current_user).execute!
if two_factor_authentication_required? && !current_user.two_factor_enabled?
two_factor_authentication_reason(
@@ -41,7 +41,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
if current_user.validate_and_consume_otp!(params[:pin_code])
- Users::UpdateService.new(current_user, otp_required_for_login: true).execute! do |user|
+ Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
@codes = user.generate_otp_backup_codes!
end
@@ -70,7 +70,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def codes
- Users::UpdateService.new(current_user).execute! do |user|
+ Users::UpdateService.new(current_user, user: current_user).execute! do |user|
@codes = user.generate_otp_backup_codes!
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index d83824fef06..5d87037f012 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -10,7 +10,7 @@ class ProfilesController < Profiles::ApplicationController
def update
respond_to do |format|
- result = Users::UpdateService.new(@user, user_params).execute
+ result = Users::UpdateService.new(current_user, user_params.merge(user: @user)).execute
if result[:status] == :success
message = "Profile was successfully updated"
@@ -25,7 +25,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_private_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_authentication_token!
end
@@ -35,7 +35,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_incoming_email_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_incoming_email_token!
end
@@ -45,7 +45,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_rss_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_rss_token!
end
@@ -61,7 +61,7 @@ class ProfilesController < Profiles::ApplicationController
end
def update_username
- result = Users::UpdateService.new(@user, username: user_params[:username]).execute
+ result = Users::UpdateService.new(current_user, user: @user, username: user_params[:username]).execute
options = if result[:status] == :success
{ notice: "Username successfully changed" }
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index eb010923466..0837451cc49 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -29,13 +29,17 @@ class Projects::ArtifactsController < Projects::ApplicationController
blob = @entry.blob
conditionally_expand_blob(blob)
- respond_to do |format|
- format.html do
- render 'file'
- end
-
- format.json do
- render_blob_json(blob)
+ if blob.external_link?(build)
+ redirect_to blob.external_url(@project, build)
+ else
+ respond_to do |format|
+ format.html do
+ render 'file'
+ end
+
+ format.json do
+ render_blob_json(blob)
+ end
end
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 2b8f3977e6e..183a6f88a6a 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -41,6 +41,8 @@ class Projects::BlobController < Projects::ApplicationController
end
format.json do
+ page_title @blob.path, @ref, @project.name_with_namespace
+
show_json
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index a9cce578366..7f03ce07dec 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -9,7 +9,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@sort = params[:sort].presence || sort_value_recently_updated
- @branches = BranchesFinder.new(@repository, params).execute
+ @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
respond_to do |format|
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
new file mode 100644
index 00000000000..03019b0becc
--- /dev/null
+++ b/app/controllers/projects/clusters_controller.rb
@@ -0,0 +1,136 @@
+class Projects::ClustersController < Projects::ApplicationController
+ before_action :cluster, except: [:login, :index, :new, :create]
+ before_action :authorize_read_cluster!
+ before_action :authorize_create_cluster!, only: [:new, :create]
+ before_action :authorize_google_api, only: [:new, :create]
+ before_action :authorize_update_cluster!, only: [:update]
+ before_action :authorize_admin_cluster!, only: [:destroy]
+
+ def index
+ if project.cluster
+ redirect_to project_cluster_path(project, project.cluster)
+ else
+ redirect_to new_project_cluster_path(project)
+ end
+ end
+
+ def login
+ begin
+ state = generate_session_key_redirect(namespace_project_clusters_url.to_s)
+
+ @authorize_url = GoogleApi::CloudPlatform::Client.new(
+ nil, callback_google_api_auth_url,
+ state: state).authorize_url
+ rescue GoogleApi::Auth::ConfigMissingError
+ # no-op
+ end
+ end
+
+ def new
+ @cluster = project.build_cluster
+ end
+
+ def create
+ @cluster = Ci::CreateClusterService
+ .new(project, current_user, create_params)
+ .execute(token_in_session)
+
+ if @cluster.persisted?
+ redirect_to project_cluster_path(project, @cluster)
+ else
+ render :new
+ end
+ end
+
+ def status
+ respond_to do |format|
+ format.json do
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+ render json: ClusterSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent_status(@cluster)
+ end
+ end
+ end
+
+ def show
+ end
+
+ def update
+ Ci::UpdateClusterService
+ .new(project, current_user, update_params)
+ .execute(cluster)
+
+ if cluster.valid?
+ flash[:notice] = "Cluster was successfully updated."
+ redirect_to project_cluster_path(project, project.cluster)
+ else
+ render :show
+ end
+ end
+
+ def destroy
+ if cluster.destroy
+ flash[:notice] = "Cluster integration was successfully removed."
+ redirect_to project_clusters_path(project), status: 302
+ else
+ flash[:notice] = "Cluster integration was not removed."
+ render :show
+ end
+ end
+
+ private
+
+ def cluster
+ @cluster ||= project.cluster.present(current_user: current_user)
+ end
+
+ def create_params
+ params.require(:cluster).permit(
+ :gcp_project_id,
+ :gcp_cluster_zone,
+ :gcp_cluster_name,
+ :gcp_cluster_size,
+ :gcp_machine_type,
+ :project_namespace,
+ :enabled)
+ end
+
+ def update_params
+ params.require(:cluster).permit(
+ :project_namespace,
+ :enabled)
+ end
+
+ def authorize_google_api
+ unless GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
+ .validate_token(expires_at_in_session)
+ redirect_to action: 'login'
+ end
+ end
+
+ def token_in_session
+ @token_in_session ||=
+ session[GoogleApi::CloudPlatform::Client.session_key_for_token]
+ end
+
+ def expires_at_in_session
+ @expires_at_in_session ||=
+ session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
+ end
+
+ def generate_session_key_redirect(uri)
+ GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
+ session[key] = uri
+ end
+ end
+
+ def authorize_update_cluster!
+ access_denied! unless can?(current_user, :update_cluster, cluster)
+ end
+
+ def authorize_admin_cluster!
+ access_denied! unless can?(current_user, :admin_cluster, cluster)
+ end
+end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 7d0e2b3e2ef..95d7a02e9e9 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -9,6 +9,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
alias_method :user, :actor
+ alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index a3ec79a56d9..b7a108a0ebd 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue
- before_action :authorize_update_issue!, only: [:edit, :update, :move]
+ before_action :authorize_update_issue!, only: [:update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
@@ -63,10 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
respond_with(@issue)
end
- def edit
- respond_with(@issue)
- end
-
def show
@noteable = @issue
@note = @project.notes.new(noteable: @issue)
@@ -126,10 +122,6 @@ class Projects::IssuesController < Projects::ApplicationController
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
respond_to do |format|
- format.html do
- recaptcha_check_with_fallback { render :edit }
- end
-
format.json do
render_issue_json
end
@@ -286,6 +278,7 @@ class Projects::IssuesController < Projects::ApplicationController
state_event
task_num
lock_version
+ discussion_locked
] + [{ label_ids: [], assignee_ids: [] }]
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 96abdac91b6..1b985ea9763 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -11,7 +11,7 @@ class Projects::JobsController < Projects::ApplicationController
def index
@scope = params[:scope]
@all_builds = project.builds.relevant
- @builds = @all_builds.order('created_at DESC')
+ @builds = @all_builds.order('ci_builds.id DESC')
@builds =
case @scope
when 'pending'
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index 1b0d3aab3fa..536f908d2c5 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -2,6 +2,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
include LfsRequest
skip_before_action :lfs_check_access!, only: [:deprecated]
+ before_action :lfs_check_batch_operation!, only: [:batch]
def batch
unless objects.present?
@@ -90,4 +91,21 @@ class Projects::LfsApiController < Projects::GitHttpClientController
}
}
end
+
+ def lfs_check_batch_operation!
+ if upload_request? && Gitlab::Database.read_only?
+ render(
+ json: {
+ message: lfs_read_only_message
+ },
+ content_type: 'application/vnd.git-lfs+json',
+ status: 403
+ )
+ end
+ end
+
+ # Overridden in EE
+ def lfs_read_only_message
+ _('You cannot write to this read-only GitLab instance.')
+ end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 6602b204fcb..0e71977a58a 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -13,7 +13,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
- @merge_request.ensure_ref_fetched
+ @merge_request.ensure_ref_fetched if Gitlab::Database.read_write?
end
def merge_request_params
@@ -34,6 +34,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:target_project_id,
:task_num,
:title,
+ :discussion_locked,
label_ids: []
]
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 1096afbb798..99dc3dda9e7 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -120,10 +120,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end
def selected_target_project
- if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
+ if @project.id.to_s == params[:target_project_id] || !@project.forked?
@project
+ elsif params[:target_project_id].present?
+ MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: @project)
+ .execute.find(params[:target_project_id])
else
- @project.forked_project_link.forked_from_project
+ @project.forked_from_project
end
end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 41a13f6f577..ef7d047b1ad 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -66,7 +66,16 @@ class Projects::NotesController < Projects::ApplicationController
params.merge(last_fetched_at: last_fetched_at)
end
+ def authorize_admin_note!
+ return access_denied! unless can?(current_user, :admin_note, note)
+ end
+
def authorize_resolve_note!
return access_denied! unless can?(current_user, :resolve_note, note)
end
+
+ def authorize_create_note!
+ return unless noteable.lockable?
+ access_denied! unless can?(current_user, :create_note, noteable)
+ end
end
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 71e7dc70a4d..32c0fc6d14a 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -6,17 +6,26 @@ module Projects
def index
@images = project.container_repositories
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: ContainerRepositoriesSerializer
+ .new(project: project, current_user: current_user)
+ .represent(@images)
+ end
+ end
end
def destroy
if image.destroy
- redirect_to project_container_registry_index_path(@project),
- status: 302,
- notice: 'Image repository has been removed successfully!'
+ respond_to do |format|
+ format.json { head :no_content }
+ end
else
- redirect_to project_container_registry_index_path(@project),
- status: 302,
- alert: 'Failed to remove image repository!'
+ respond_to do |format|
+ format.json { head :bad_request }
+ end
end
end
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index ae72bd03cfb..e602aa3f393 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -3,20 +3,35 @@ module Projects
class TagsController < ::Projects::Registry::ApplicationController
before_action :authorize_update_container_image!, only: [:destroy]
+ def index
+ respond_to do |format|
+ format.json do
+ render json: ContainerTagsSerializer
+ .new(project: @project, current_user: @current_user)
+ .with_pagination(request, response)
+ .represent(tags)
+ end
+ end
+ end
+
def destroy
if tag.delete
- redirect_to project_container_registry_index_path(@project),
- status: 302,
- notice: 'Registry tag has been removed successfully!'
+ respond_to do |format|
+ format.json { head :no_content }
+ end
else
- redirect_to project_container_registry_index_path(@project),
- status: 302,
- alert: 'Failed to remove registry tag!'
+ respond_to do |format|
+ format.json { head :bad_request }
+ end
end
end
private
+ def tags
+ Kaminari::PaginatableArray.new(image.tags, limit: 15)
+ end
+
def image
@image ||= project.container_repositories
.find(params[:repository_id])
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index f3784f4e07c..f3719059f88 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -35,6 +35,8 @@ class Projects::TreeController < Projects::ApplicationController
end
format.json do
+ page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
+
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 968d880886c..a8ebdf5a4a9 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -18,16 +18,12 @@ class Projects::WikisController < Projects::ApplicationController
response.headers['Content-Security-Policy'] = "default-src 'none'"
response.headers['X-Content-Security-Policy'] = "default-src 'none'"
- if file.on_disk?
- send_file file.on_disk_path, disposition: 'inline'
- else
- send_data(
- file.raw_data,
- type: file.mime_type,
- disposition: 'inline',
- filename: file.name
- )
- end
+ send_data(
+ file.raw_data,
+ type: file.mime_type,
+ disposition: 'inline',
+ filename: file.name
+ )
else
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b13034d3333..a738ca9f361 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController
:tag_list,
:visibility_level,
:template_name,
+ :merge_method,
project_feature_attributes: %i[
builds_access_level
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 1bc6520370a..d9142311b6f 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -25,27 +25,44 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
- current_user.delete_async(deleted_by: current_user)
-
- respond_to do |format|
- format.html do
- session.try(:destroy)
- redirect_to new_user_session_path, status: 302, notice: "Account scheduled for removal."
- end
+ if destroy_confirmation_valid?
+ current_user.delete_async(deleted_by: current_user)
+ session.try(:destroy)
+ redirect_to new_user_session_path, status: 303, notice: s_('Profiles|Account scheduled for removal.')
+ else
+ redirect_to profile_account_path, status: 303, alert: destroy_confirmation_failure_message
end
end
protected
+ def destroy_confirmation_valid?
+ if current_user.confirm_deletion_with_password?
+ current_user.valid_password?(params[:password])
+ else
+ current_user.username == params[:username]
+ end
+ end
+
+ def destroy_confirmation_failure_message
+ if current_user.confirm_deletion_with_password?
+ s_('Profiles|Invalid password')
+ else
+ s_('Profiles|Invalid username')
+ end
+ end
+
def build_resource(hash = nil)
super
end
def after_sign_up_path_for(user)
+ Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}")
user.confirmed? ? dashboard_projects_path : users_almost_there_path
end
- def after_inactive_sign_up_path_for(_resource)
+ def after_inactive_sign_up_path_for(resource)
+ Gitlab::AppLogger.info("User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:false")
users_almost_there_path
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index be6491d042c..c01be42c3ee 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -8,11 +8,12 @@ class SessionsController < Devise::SessionsController
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create]
- prepend_before_action :store_redirect_path, only: [:new]
-
+ prepend_before_action :store_redirect_uri, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
+ after_action :log_failed_login, only: [:new], if: :failed_login?
+
def new
set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers
@@ -29,12 +30,13 @@ class SessionsController < Devise::SessionsController
end
# hide the signed-in notification
flash[:notice] = nil
- log_audit_event(current_user, with: authentication_method)
+ log_audit_event(current_user, resource, with: authentication_method)
log_user_activity(current_user)
end
end
def destroy
+ Gitlab::AppLogger.info("User Logout: username=#{current_user.username} ip=#{request.remote_ip}")
super
# hide the signed_out notice
flash[:notice] = nil
@@ -42,6 +44,14 @@ class SessionsController < Devise::SessionsController
private
+ def log_failed_login
+ Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
+ end
+
+ def failed_login?
+ (options = env["warden.options"]) && options[:action] == "unauthenticated"
+ end
+
def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
@@ -55,7 +65,7 @@ class SessionsController < Devise::SessionsController
return unless user && user.require_password_creation?
- Users::UpdateService.new(user).execute do |user|
+ Users::UpdateService.new(current_user, user: user).execute do |user|
@token = user.generate_reset_token
end
@@ -75,28 +85,36 @@ class SessionsController < Devise::SessionsController
end
end
- def store_redirect_path
- redirect_path =
+ def stored_redirect_uri
+ @redirect_to ||= stored_location_for(:redirect)
+ end
+
+ def store_redirect_uri
+ redirect_uri =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
- referer_uri = URI(request.referer)
- if referer_uri.host == Gitlab.config.gitlab.host
- referer_uri.request_uri
- else
- request.fullpath
- end
+ URI(request.referer)
else
- request.fullpath
+ URI(request.url)
end
# Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully.
- unless URI(redirect_path).path == new_user_session_path
- store_location_for(:redirect, redirect_path)
- end
+ return true if redirect_uri.path == new_user_session_path
+
+ redirect_to = redirect_uri.to_s if redirect_allowed_to?(redirect_uri)
+
+ @redirect_to = redirect_to
+ store_location_for(:redirect, redirect_to)
+ end
+
+ # Overridden in EE
+ def redirect_allowed_to?(uri)
+ uri.host == Gitlab.config.gitlab.host &&
+ uri.port == Gitlab.config.gitlab.port
end
def two_factor_enabled?
- find_user.try(:two_factor_enabled?)
+ find_user&.two_factor_enabled?
end
def auto_sign_in_with_provider
@@ -123,7 +141,8 @@ class SessionsController < Devise::SessionsController
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
- def log_audit_event(user, options = {})
+ def log_audit_event(user, resource, options = {})
+ Gitlab::AppLogger.info("Successful Login: username=#{resource.username} ip=#{request.remote_ip} method=#{options[:with]} admin=#{resource.admin?}")
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
diff --git a/app/finders/concerns/custom_attributes_filter.rb b/app/finders/concerns/custom_attributes_filter.rb
new file mode 100644
index 00000000000..5bbf9ca242d
--- /dev/null
+++ b/app/finders/concerns/custom_attributes_filter.rb
@@ -0,0 +1,20 @@
+module CustomAttributesFilter
+ def by_custom_attributes(items)
+ return items unless params[:custom_attributes].is_a?(Hash)
+ return items unless Ability.allowed?(current_user, :read_custom_attribute)
+
+ association = items.reflect_on_association(:custom_attributes)
+ attributes_table = association.klass.arel_table
+ attributable_table = items.model.arel_table
+
+ custom_attributes = association.klass.select('true').where(
+ attributes_table[association.foreign_key]
+ .eq(attributable_table[association.association_primary_key])
+ )
+
+ # perform a subquery for each attribute to be filtered
+ params[:custom_attributes].inject(items) do |scope, (key, value)|
+ scope.where('EXISTS (?)', custom_attributes.where(key: key, value: value))
+ end
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0a2e3c709d9..24c07f3dc70 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -25,6 +25,28 @@ class IssuableFinder
NONE = '0'.freeze
+ SCALAR_PARAMS = %i[
+ assignee_id
+ assignee_username
+ author_id
+ author_username
+ authorized_only
+ due_date
+ group_id
+ iids
+ label_name
+ milestone_title
+ non_archived
+ project_id
+ scope
+ search
+ sort
+ state
+ ].freeze
+ ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
+
+ VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze
+
attr_accessor :current_user, :params
def initialize(current_user, params = {})
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
new file mode 100644
index 00000000000..189eb3847eb
--- /dev/null
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -0,0 +1,18 @@
+class MergeRequestTargetProjectFinder
+ attr_reader :current_user, :source_project
+
+ def initialize(current_user: nil, source_project:)
+ @current_user = current_user
+ @source_project = source_project
+ end
+
+ def execute
+ if @source_project.fork_network
+ @source_project.fork_network.projects
+ .public_or_visible_to_user(current_user)
+ .with_feature_available_for_user(:merge_requests, current_user)
+ else
+ Project.where(id: source_project)
+ end
+ end
+end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 33f7ae90598..1a7e97004fb 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -15,6 +15,7 @@
#
class UsersFinder
include CreatedAtFilter
+ include CustomAttributesFilter
attr_accessor :current_user, :params
@@ -32,6 +33,7 @@ class UsersFinder
users = by_external_identity(users)
users = by_external(users)
users = by_created_at(users)
+ users = by_custom_attributes(users)
users
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 62ac208f16a..7112c6ee470 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -79,6 +79,6 @@ module BoardsHelper
end
def boards_link_text
- _("Board")
+ s_("IssueBoards|Board")
end
end
diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb
index ee1b7ed083e..e88fe6bcd7e 100644
--- a/app/helpers/breadcrumbs_helper.rb
+++ b/app/helpers/breadcrumbs_helper.rb
@@ -10,11 +10,7 @@ module BreadcrumbsHelper
def breadcrumb_title_link
return @breadcrumb_link if @breadcrumb_link
- if controller.available_action?(:index)
- url_for(action: "index")
- else
- request.path
- end
+ request.path
end
def breadcrumb_title(title)
@@ -25,7 +21,7 @@ module BreadcrumbsHelper
def breadcrumb_list_item(link)
content_tag "li" do
- link + icon("angle-right", class: "breadcrumbs-list-angle")
+ link + sprite_icon("angle-right", size: 8, css_class: "breadcrumbs-list-angle")
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 28f591a4e22..4e4a66e8a02 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -33,19 +33,21 @@ module DiffHelper
end
def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
- content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
- cls = ['diff-line-num', 'unfold', 'js-unfold']
- cls << 'js-unfold-bottom' if bottom
+ content_line_class = %w[line_content match]
+ content_line_class << 'parallel' if view == :parallel
+
+ line_num_class = %w[diff-line-num unfold js-unfold]
+ line_num_class << 'js-unfold-bottom' if bottom
html = ''
if old_pos
- html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
- html << content unless view == :inline
+ html << content_tag(:td, '...', class: [*line_num_class, 'old_line'], data: { linenumber: old_pos })
+ html << content_tag(:td, text, class: [*content_line_class, 'left-side']) if view == :parallel
end
if new_pos
- html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
- html << content
+ html << content_tag(:td, '...', class: [*line_num_class, 'new_line'], data: { linenumber: new_pos })
+ html << content_tag(:td, text, class: [*content_line_class, ('right-side' if view == :parallel)])
end
html.html_safe
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index b331693c789..fd88e0d794a 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -1,13 +1,15 @@
module EventsHelper
ICON_NAMES_BY_EVENT_TYPE = {
- 'pushed to' => 'icon_commit',
- 'pushed new' => 'icon_commit',
- 'created' => 'icon_status_open',
- 'opened' => 'icon_status_open',
- 'closed' => 'icon_status_closed',
- 'accepted' => 'icon_code_fork',
- 'commented on' => 'icon_comment_o',
- 'deleted' => 'icon_trash_o'
+ 'pushed to' => 'commit',
+ 'pushed new' => 'commit',
+ 'created' => 'status_open',
+ 'opened' => 'status_open',
+ 'closed' => 'status_closed',
+ 'accepted' => 'fork',
+ 'commented on' => 'comment',
+ 'deleted' => 'remove',
+ 'imported' => 'import',
+ 'joined' => 'users'
}.freeze
def link_to_author(event, self_added: false)
@@ -197,7 +199,7 @@ module EventsHelper
def icon_for_event(note)
icon_name = ICON_NAMES_BY_EVENT_TYPE[note]
- custom_icon(icon_name) if icon_name
+ sprite_icon(icon_name) if icon_name
end
def icon_for_profile_event(event)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 08e6443bd0f..ec779c1c447 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -24,9 +24,9 @@ module IconsHelper
end
def sprite_icon(icon_name, size: nil, css_class: nil)
- css_classes = size ? "s#{size}" : nil
+ css_classes = size ? "s#{size}" : ""
css_classes << " #{css_class}" unless css_class.blank?
- content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes)
+ content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{image_path('icons.svg')}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
def audit_icon(names, options = {})
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index df390dd5aab..7713fb0b9f8 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -248,16 +248,25 @@ module IssuablesHelper
Gitlab::IssuablesCountForState.new(finder)[state]
end
- def close_issuable_url(issuable)
- issuable_url(issuable, close_reopen_params(issuable, :close))
+ def close_issuable_path(issuable)
+ issuable_path(issuable, close_reopen_params(issuable, :close))
end
- def reopen_issuable_url(issuable)
- issuable_url(issuable, close_reopen_params(issuable, :reopen))
+ def reopen_issuable_path(issuable)
+ issuable_path(issuable, close_reopen_params(issuable, :reopen))
end
- def close_reopen_issuable_url(issuable, should_inverse = false)
- issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
+ def close_reopen_issuable_path(issuable, should_inverse = false)
+ issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
+ end
+
+ def issuable_path(issuable, *options)
+ case issuable
+ when Issue
+ issue_path(issuable, *options)
+ when MergeRequest
+ merge_request_path(issuable, *options)
+ end
end
def issuable_url(issuable, *options)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index c31023f2d9a..5b2c58d193d 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -73,7 +73,8 @@ module MergeRequestsHelper
end
def target_projects(project)
- [project, project.default_merge_request_target].uniq
+ MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: project)
+ .execute
end
def merge_request_button_visibility(merge_request, closed)
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index ce028195e51..c219aa3d6a9 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -130,8 +130,12 @@ module NotesHelper
end
def can_create_note?
+ issuable = @issue || @merge_request
+
if @snippet.is_a?(PersonalSnippet)
can?(current_user, :comment_personal_snippet, @snippet)
+ elsif issuable
+ can?(current_user, :create_note, issuable)
else
can?(current_user, :create_note, @project)
end
diff --git a/app/helpers/numbers_helper.rb b/app/helpers/numbers_helper.rb
new file mode 100644
index 00000000000..45bd3606076
--- /dev/null
+++ b/app/helpers/numbers_helper.rb
@@ -0,0 +1,11 @@
+module NumbersHelper
+ def limited_counter_with_delimiter(resource, **options)
+ limit = options.fetch(:limit, 1000).to_i
+ count = resource.limit(limit + 1).count(:all)
+ if count > limit
+ number_with_delimiter(count - 1, options) + '+'
+ else
+ number_with_delimiter(count, options)
+ end
+ end
+end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 5946c475835..18b9bf214a3 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -9,7 +9,7 @@ module PageLayoutHelper
end
# Segments are seperated by middot
- @page_title.join(" \u00b7 ")
+ @page_title.join(" · ")
end
# Define or get a description for the current page
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 21fb17e06d6..20e050195ea 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -21,11 +21,14 @@ module ProjectsHelper
classes = %W[avatar avatar-inline s#{opts[:size]}]
classes << opts[:avatar_class] if opts[:avatar_class]
- image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: classes, alt: '')
+ avatar = avatar_icon(author, opts[:size])
+ src = opts[:lazy_load] ? nil : avatar
+
+ image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
end
def link_to_member(project, author, opts = {}, &block)
- default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false }
+ default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false, lazy_load: false }
opts = default_opts.merge(opts)
return "(deleted)" unless author
@@ -290,6 +293,7 @@ module ProjectsHelper
snippets: :read_project_snippet,
settings: :admin_project,
builds: :read_build,
+ clusters: :read_cluster,
labels: :read_label,
issues: :read_issue,
project_members: :read_project_member,
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index d7eaf6ce24d..00fe67d6ffb 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -19,7 +19,9 @@ module SystemNoteHelper
'discussion' => 'comment',
'moved' => 'arrow-right',
'outdated' => 'pencil',
- 'duplicate' => 'issue-duplicate'
+ 'duplicate' => 'issue-duplicate',
+ 'locked' => 'lock',
+ 'unlocked' => 'lock-open'
}.freeze
def system_note_icon_name(note)
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index c401030e34a..4f5edeb9bda 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -7,12 +7,6 @@ module Emails
mail(to: @user.notification_email, subject: subject("Account was created for you"))
end
- def new_email_email(email_id)
- @email = Email.find(email_id)
- @current_user = @user = @email.user
- mail(to: @user.notification_email, subject: subject("Email was added to your account"))
- end
-
def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id)
diff --git a/app/models/ci/artifact_blob.rb b/app/models/ci/artifact_blob.rb
index b35febc9ac5..8b66531ec7b 100644
--- a/app/models/ci/artifact_blob.rb
+++ b/app/models/ci/artifact_blob.rb
@@ -2,6 +2,8 @@ module Ci
class ArtifactBlob
include BlobLike
+ EXTENTIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze
+
attr_reader :entry
def initialize(entry)
@@ -17,6 +19,7 @@ module Ci
def size
entry.metadata[:size]
end
+ alias_method :external_size, :size
def data
"Build artifact #{path}"
@@ -30,6 +33,27 @@ module Ci
:build_artifact
end
- alias_method :external_size, :size
+ def external_url(project, job)
+ return unless external_link?(job)
+
+ components = project.full_path_components
+ components << "-/jobs/#{job.id}/artifacts/file/#{path}"
+ artifact_path = components[1..-1].join('/')
+
+ "#{pages_config.protocol}://#{components[0]}.#{pages_config.host}/#{artifact_path}"
+ end
+
+ def external_link?(job)
+ pages_config.enabled &&
+ pages_config.artifacts_server &&
+ EXTENTIONS_SERVED_BY_PAGES.include?(File.extname(name)) &&
+ job.project.public?
+ end
+
+ private
+
+ def pages_config
+ Gitlab.config.pages
+ end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ee544d8ac56..6ca46ae89c1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -11,6 +11,7 @@ module Ci
has_many :deployments, as: :deployable
has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment'
+ has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
# The "environment" field for builds is a String, and is the unexpanded name
def persisted_environment
@@ -229,6 +230,10 @@ module Ci
variables
end
+ def features
+ { trace_sections: true }
+ end
+
def merge_request
return @merge_request if defined?(@merge_request)
@@ -261,6 +266,10 @@ module Ci
update_attributes(coverage: coverage) if coverage.present?
end
+ def parse_trace_sections!
+ ExtractSectionsFromBuildTraceService.new(project, user).execute(self)
+ end
+
def trace
Gitlab::Ci::Trace.new(self)
end
diff --git a/app/models/ci/build_trace_section.rb b/app/models/ci/build_trace_section.rb
new file mode 100644
index 00000000000..ccdb95546c8
--- /dev/null
+++ b/app/models/ci/build_trace_section.rb
@@ -0,0 +1,11 @@
+module Ci
+ class BuildTraceSection < ActiveRecord::Base
+ extend Gitlab::Ci::Model
+
+ belongs_to :build, class_name: 'Ci::Build'
+ belongs_to :project
+ belongs_to :section_name, class_name: 'Ci::BuildTraceSectionName'
+
+ validates :section_name, :build, :project, presence: true, allow_blank: false
+ end
+end
diff --git a/app/models/ci/build_trace_section_name.rb b/app/models/ci/build_trace_section_name.rb
new file mode 100644
index 00000000000..0fdcb1ea329
--- /dev/null
+++ b/app/models/ci/build_trace_section_name.rb
@@ -0,0 +1,11 @@
+module Ci
+ class BuildTraceSectionName < ActiveRecord::Base
+ extend Gitlab::Ci::Model
+
+ belongs_to :project
+ has_many :trace_sections, class_name: 'Ci::BuildTraceSection', foreign_key: :section_name_id
+
+ validates :name, :project, presence: true, allow_blank: false
+ validates :name, uniqueness: { scope: :project_id }
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index acaa028eaa2..cf3ce3c9e54 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -5,6 +5,7 @@ module Ci
include Importable
include AfterCommitQueue
include Presentable
+ include Gitlab::OptimisticLocking
belongs_to :project
belongs_to :user
@@ -58,6 +59,11 @@ module Ci
auto_devops_source: 2
}
+ enum failure_reason: {
+ unknown_failure: 0,
+ config_error: 1
+ }
+
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
@@ -109,6 +115,12 @@ module Ci
pipeline.auto_canceled_by = nil
end
+ before_transition any => :failed do |pipeline, transition|
+ transition.args.first.try do |reason|
+ pipeline.failure_reason = reason
+ end
+ end
+
after_transition [:created, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
@@ -263,7 +275,7 @@ module Ci
end
def cancel_running
- Gitlab::OptimisticLocking.retry_lock(cancelable_statuses) do |cancelable|
+ retry_optimistic_lock(cancelable_statuses) do |cancelable|
cancelable.find_each do |job|
yield(job) if block_given?
job.cancel
@@ -312,6 +324,10 @@ module Ci
@stage_seeds ||= config_processor.stage_seeds(self)
end
+ def seeds_size
+ @seeds_size ||= stage_seeds.sum(&:size)
+ end
+
def has_kubernetes_active?
project.kubernetes_service&.active?
end
@@ -403,7 +419,7 @@ module Ci
end
def update_status
- Gitlab::OptimisticLocking.retry_lock(self) do
+ retry_optimistic_lock(self) do
case latest_builds_status
when 'pending' then enqueue
when 'running' then run
@@ -434,7 +450,7 @@ module Ci
def update_duration
return unless started_at
- self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
+ self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
end
def execute_hooks
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2ae8890c1b3..6dba154a6ea 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -25,8 +25,8 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000
- # The SHA can be between 7 and 40 hex characters.
- COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
+ MIN_SHA_LENGTH = 7
+ COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
@@ -53,7 +53,7 @@ class Commit
# Truncate sha to 8 characters
def truncate_sha(sha)
- sha[0..7]
+ sha[0..MIN_SHA_LENGTH]
end
def max_diff_options
@@ -100,7 +100,7 @@ class Commit
def self.reference_pattern
@reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?<commit>\h{7,40})
+ (?<commit>#{COMMIT_SHA_PATTERN})
}x
end
@@ -216,9 +216,8 @@ class Commit
@raw.respond_to?(method, include_private) || super
end
- # Truncate sha to 8 characters
def short_id
- @raw.short_id(7)
+ @raw.short_id(MIN_SHA_LENGTH)
end
def diff_refs
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 193e459977a..9417033d1f6 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -59,7 +59,7 @@ module CacheMarkdownField
# Update every column in a row if any one is invalidated, as we only store
# one version per row
- def refresh_markdown_cache!(do_update: false)
+ def refresh_markdown_cache
options = { skip_project_check: skip_project_check? }
updates = cached_markdown_fields.markdown_fields.map do |markdown_field|
@@ -71,8 +71,14 @@ module CacheMarkdownField
updates['cached_markdown_version'] = CacheMarkdownField::CACHE_VERSION
updates.each {|html_field, data| write_attribute(html_field, data) }
+ end
+
+ def refresh_markdown_cache!
+ updates = refresh_markdown_cache
+
+ return unless persisted? && Gitlab::Database.read_write?
- update_columns(updates) if persisted? && do_update
+ update_columns(updates)
end
def cached_html_up_to_date?(markdown_field)
@@ -124,8 +130,8 @@ module CacheMarkdownField
end
# Using before_update here conflicts with elasticsearch-model somehow
- before_create :refresh_markdown_cache!, if: :invalidated_markdown_cache?
- before_update :refresh_markdown_cache!, if: :invalidated_markdown_cache?
+ before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
+ before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
end
class_methods do
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index eee1a36ac6b..f5cbb3becad 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -28,6 +28,10 @@ module DiscussionOnDiff
true
end
+ def file_new_path
+ first_note.position.new_path
+ end
+
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 3803e18a96e..7c3ed96bc28 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -81,6 +81,7 @@ module HasStatus
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
+ scope :alive, -> { where(status: [:created, :pending, :running]) }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 1c4ddabcad5..5d75b2aa6a3 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -74,4 +74,8 @@ module Noteable
def discussions_can_be_resolved_by?(user)
discussions_to_be_resolved.all? { |discussion| discussion.can_resolve?(user) }
end
+
+ def lockable?
+ [MergeRequest, Issue].include?(self.class)
+ end
end
diff --git a/app/models/concerns/repository_mirroring.rb b/app/models/concerns/repository_mirroring.rb
index fed336c29d6..f6aba91bc4c 100644
--- a/app/models/concerns/repository_mirroring.rb
+++ b/app/models/concerns/repository_mirroring.rb
@@ -1,11 +1,26 @@
module RepositoryMirroring
- def set_remote_as_mirror(name)
- config = raw_repository.rugged.config
+ IMPORT_HEAD_REFS = '+refs/heads/*:refs/heads/*'.freeze
+ IMPORT_TAG_REFS = '+refs/tags/*:refs/tags/*'.freeze
+ def set_remote_as_mirror(name)
# This is used to define repository as equivalent as "git clone --mirror"
- config["remote.#{name}.fetch"] = 'refs/*:refs/*'
- config["remote.#{name}.mirror"] = true
- config["remote.#{name}.prune"] = true
+ raw_repository.rugged.config["remote.#{name}.fetch"] = 'refs/*:refs/*'
+ raw_repository.rugged.config["remote.#{name}.mirror"] = true
+ raw_repository.rugged.config["remote.#{name}.prune"] = true
+ end
+
+ def set_import_remote_as_mirror(remote_name)
+ # Add first fetch with Rugged so it does not create its own.
+ raw_repository.rugged.config["remote.#{remote_name}.fetch"] = IMPORT_HEAD_REFS
+
+ add_remote_fetch_config(remote_name, IMPORT_TAG_REFS)
+
+ raw_repository.rugged.config["remote.#{remote_name}.mirror"] = true
+ raw_repository.rugged.config["remote.#{remote_name}.prune"] = true
+ end
+
+ def add_remote_fetch_config(remote_name, refspec)
+ run_git(%W[config --add remote.#{remote_name}.fetch #{refspec}])
end
def fetch_mirror(remote, url)
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index f5048d17d80..22fde2eb134 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -106,6 +106,10 @@ module Routable
RequestStore[full_path_key] ||= uncached_full_path
end
+ def full_path_components
+ full_path.split('/')
+ end
+
def expires_full_path_cache
RequestStore.delete(full_path_key) if RequestStore.active?
@full_path = nil
@@ -152,6 +156,8 @@ module Routable
end
def update_route
+ return if Gitlab::Database.read_only?
+
prepare_route
route.save
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index a7d5de48c66..ec3543f7053 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -43,15 +43,17 @@ module TokenAuthenticatable
write_attribute(token_field, token) if token
end
+ # Returns a token, but only saves when the database is in read & write mode
define_method("ensure_#{token_field}!") do
send("reset_#{token_field}!") if read_attribute(token_field).blank? # rubocop:disable GitlabSecurity/PublicSend
read_attribute(token_field)
end
+ # Resets the token, but only saves when the database is in read & write mode
define_method("reset_#{token_field}!") do
write_new_token(token_field)
- save!
+ save! if Gitlab::Database.read_write?
end
end
end
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 07c4846e2ac..6eba87da1a1 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -11,6 +11,8 @@ class DiffDiscussion < Discussion
delegate :position,
:original_position,
:change_position,
+ :on_text?,
+ :on_image?,
to: :first_note
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index e9a60e6ce09..d88a92dc027 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -12,8 +12,8 @@ class DiffNote < Note
validates :original_position, presence: true
validates :position, presence: true
- validates :diff_line, presence: true
- validates :line_code, presence: true, line_code: true
+ validates :diff_line, presence: true, if: :on_text?
+ validates :line_code, presence: true, line_code: true, if: :on_text?
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
validate :positions_complete
validate :verify_supported
@@ -43,6 +43,14 @@ class DiffNote < Note
end
end
+ def on_text?
+ position.position_type == "text"
+ end
+
+ def on_image?
+ position.position_type == "image"
+ end
+
def diff_file
@diff_file ||= self.original_position.diff_file(self.project.repository)
end
@@ -56,6 +64,8 @@ class DiffNote < Note
end
def original_line_code
+ return unless on_text?
+
self.diff_file.line_code(self.diff_line)
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index b80da7b246a..437df923d2d 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -66,6 +66,10 @@ class Discussion
@context_noteable = context_noteable
end
+ def on_image?
+ false
+ end
+
def ==(other)
other.class == self.class &&
other.context_noteable == self.context_noteable &&
diff --git a/app/models/email.rb b/app/models/email.rb
index 826d4f16edb..384f38f2db7 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -7,6 +7,13 @@ class Email < ActiveRecord::Base
validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? }
+ scope :confirmed, -> { where.not(confirmed_at: nil) }
+
+ after_commit :update_invalid_gpg_signatures, if: -> { previous_changes.key?('confirmed_at') }
+
+ devise :confirmable
+ self.reconfirmable = false # currently email can't be changed, no need to reconfirm
+
def email=(value)
write_attribute(:email, value.downcase.strip)
end
@@ -14,4 +21,9 @@ class Email < ActiveRecord::Base
def unique_email
self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
end
+
+ # once email is confirmed, update the gpg signatures
+ def update_invalid_gpg_signatures
+ user.update_invalid_gpg_signatures if confirmed?
+ end
end
diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb
new file mode 100644
index 00000000000..218e37a5312
--- /dev/null
+++ b/app/models/fork_network.rb
@@ -0,0 +1,15 @@
+class ForkNetwork < ActiveRecord::Base
+ belongs_to :root_project, class_name: 'Project'
+ has_many :fork_network_members
+ has_many :projects, through: :fork_network_members
+
+ after_create :add_root_as_member, if: :root_project
+
+ def add_root_as_member
+ projects << root_project
+ end
+
+ def find_forks_in(other_projects)
+ projects.where(id: other_projects)
+ end
+end
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
new file mode 100644
index 00000000000..6a9b52a1ef8
--- /dev/null
+++ b/app/models/fork_network_member.rb
@@ -0,0 +1,7 @@
+class ForkNetworkMember < ActiveRecord::Base
+ belongs_to :fork_network
+ belongs_to :project
+ belongs_to :forked_from_project, class_name: 'Project'
+
+ validates :fork_network, :project, presence: true
+end
diff --git a/app/models/gcp/cluster.rb b/app/models/gcp/cluster.rb
new file mode 100644
index 00000000000..18bd6a6dcb4
--- /dev/null
+++ b/app/models/gcp/cluster.rb
@@ -0,0 +1,113 @@
+module Gcp
+ class Cluster < ActiveRecord::Base
+ extend Gitlab::Gcp::Model
+ include Presentable
+
+ belongs_to :project, inverse_of: :cluster
+ belongs_to :user
+ belongs_to :service
+
+ default_value_for :gcp_cluster_zone, 'us-central1-a'
+ default_value_for :gcp_cluster_size, 3
+ default_value_for :gcp_machine_type, 'n1-standard-4'
+
+ attr_encrypted :password,
+ mode: :per_attribute_iv,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
+
+ attr_encrypted :kubernetes_token,
+ mode: :per_attribute_iv,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
+
+ attr_encrypted :gcp_token,
+ mode: :per_attribute_iv,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
+
+ validates :gcp_project_id,
+ length: 1..63,
+ format: {
+ with: Gitlab::Regex.kubernetes_namespace_regex,
+ message: Gitlab::Regex.kubernetes_namespace_regex_message
+ }
+
+ validates :gcp_cluster_name,
+ length: 1..63,
+ format: {
+ with: Gitlab::Regex.kubernetes_namespace_regex,
+ message: Gitlab::Regex.kubernetes_namespace_regex_message
+ }
+
+ validates :gcp_cluster_zone, presence: true
+
+ validates :gcp_cluster_size,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than: 0
+ }
+
+ validates :project_namespace,
+ allow_blank: true,
+ length: 1..63,
+ format: {
+ with: Gitlab::Regex.kubernetes_namespace_regex,
+ message: Gitlab::Regex.kubernetes_namespace_regex_message
+ }
+
+ # if we do not do status transition we prevent change
+ validate :restrict_modification, on: :update, unless: :status_changed?
+
+ state_machine :status, initial: :scheduled do
+ state :scheduled, value: 1
+ state :creating, value: 2
+ state :created, value: 3
+ state :errored, value: 4
+
+ event :make_creating do
+ transition any - [:creating] => :creating
+ end
+
+ event :make_created do
+ transition any - [:created] => :created
+ end
+
+ event :make_errored do
+ transition any - [:errored] => :errored
+ end
+
+ before_transition any => [:errored, :created] do |cluster|
+ cluster.gcp_token = nil
+ cluster.gcp_operation_id = nil
+ end
+
+ before_transition any => [:errored] do |cluster, transition|
+ status_reason = transition.args.first
+ cluster.status_reason = status_reason if status_reason
+ end
+ end
+
+ def project_namespace_placeholder
+ "#{project.path}-#{project.id}"
+ end
+
+ def on_creation?
+ scheduled? || creating?
+ end
+
+ def api_url
+ 'https://' + endpoint if endpoint
+ end
+
+ def restrict_modification
+ if on_creation?
+ errors.add(:base, "cannot modify during creation")
+ return false
+ end
+
+ true
+ end
+ end
+end
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 44deae4234b..44eda741679 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -9,6 +9,9 @@ class GpgKey < ActiveRecord::Base
belongs_to :user
has_many :gpg_signatures
+ has_many :subkeys, class_name: 'GpgKeySubkey'
+
+ scope :with_subkeys, -> { includes(:subkeys) }
validates :user, presence: true
@@ -36,10 +39,12 @@ class GpgKey < ActiveRecord::Base
before_validation :extract_fingerprint, :extract_primary_keyid
after_commit :update_invalid_gpg_signatures, on: :create
+ after_create :generate_subkeys
def primary_keyid
super&.upcase
end
+ alias_method :keyid, :primary_keyid
def fingerprint
super&.upcase
@@ -49,6 +54,10 @@ class GpgKey < ActiveRecord::Base
super(value&.strip)
end
+ def keyids
+ [keyid].concat(subkeys.map(&:keyid))
+ end
+
def user_infos
@user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
end
@@ -73,7 +82,7 @@ class GpgKey < ActiveRecord::Base
end
def verified_and_belongs_to_email?(email)
- emails_with_verified_status.fetch(email, false)
+ emails_with_verified_status.fetch(email.downcase, false)
end
def update_invalid_gpg_signatures
@@ -82,10 +91,11 @@ class GpgKey < ActiveRecord::Base
def revoke
GpgSignature
- .where(gpg_key: self)
+ .with_key_and_subkeys(self)
.where.not(verification_status: GpgSignature.verification_statuses[:unknown_key])
.update_all(
gpg_key_id: nil,
+ gpg_key_subkey_id: nil,
verification_status: GpgSignature.verification_statuses[:unknown_key],
updated_at: Time.zone.now
)
@@ -106,4 +116,12 @@ class GpgKey < ActiveRecord::Base
# only allows one key
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
end
+
+ def generate_subkeys
+ gpg_subkeys = Gitlab::Gpg.subkeys_from_key(key)
+
+ gpg_subkeys[primary_keyid]&.each do |subkey_data|
+ subkeys.create!(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint])
+ end
+ end
end
diff --git a/app/models/gpg_key_subkey.rb b/app/models/gpg_key_subkey.rb
new file mode 100644
index 00000000000..b57922aba30
--- /dev/null
+++ b/app/models/gpg_key_subkey.rb
@@ -0,0 +1,22 @@
+class GpgKeySubkey < ActiveRecord::Base
+ include ShaAttribute
+
+ sha_attribute :keyid
+ sha_attribute :fingerprint
+
+ belongs_to :gpg_key
+
+ validates :gpg_key_id, presence: true
+ validates :fingerprint, :keyid, presence: true, uniqueness: true
+
+ delegate :key, :user, :user_infos, :verified?, :verified_user_infos,
+ :verified_and_belongs_to_email?, to: :gpg_key
+
+ def keyid
+ super&.upcase
+ end
+
+ def fingerprint
+ super&.upcase
+ end
+end
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 1f047a32c84..bf88d75246f 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -15,11 +15,42 @@ class GpgSignature < ActiveRecord::Base
belongs_to :project
belongs_to :gpg_key
+ belongs_to :gpg_key_subkey
validates :commit_sha, presence: true
validates :project_id, presence: true
validates :gpg_key_primary_keyid, presence: true
+ def self.with_key_and_subkeys(gpg_key)
+ subkey_ids = gpg_key.subkeys.pluck(:id)
+
+ where(
+ arel_table[:gpg_key_id].eq(gpg_key.id).or(
+ arel_table[:gpg_key_subkey_id].in(subkey_ids)
+ )
+ )
+ end
+
+ def gpg_key=(model)
+ case model
+ when GpgKey
+ super
+ when GpgKeySubkey
+ self.gpg_key_subkey = model
+ when NilClass
+ super
+ self.gpg_key_subkey = nil
+ end
+ end
+
+ def gpg_key
+ if gpg_key_id
+ super
+ elsif gpg_key_subkey_id
+ gpg_key_subkey
+ end
+ end
+
def gpg_key_primary_keyid
super&.upcase
end
@@ -29,6 +60,8 @@ class GpgSignature < ActiveRecord::Base
end
def gpg_commit
+ return unless commit
+
Gitlab::Gpg::Commit.new(commit)
end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 0c41e34d969..f119b15c737 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -34,6 +34,7 @@ class Key < ActiveRecord::Base
value&.delete!("\n\r")
value.strip! unless value.blank?
write_attribute(:key, value)
+ @public_key = nil
end
def publishable_key
diff --git a/app/models/legacy_diff_discussion.rb b/app/models/legacy_diff_discussion.rb
index 3c1d34db5fa..80fc6304fd4 100644
--- a/app/models/legacy_diff_discussion.rb
+++ b/app/models/legacy_diff_discussion.rb
@@ -17,6 +17,14 @@ class LegacyDiffDiscussion < Discussion
true
end
+ def on_image?
+ false
+ end
+
+ def on_text?
+ true
+ end
+
def active?(*args)
return @active if @active.present?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 8d9a30397a9..292122f779e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -403,7 +403,7 @@ class MergeRequest < ActiveRecord::Base
return false unless for_fork?
return true unless source_project
- !source_project.forked_from?(target_project)
+ !source_project.in_fork_network_of?(target_project)
end
def reopenable?
@@ -415,6 +415,8 @@ class MergeRequest < ActiveRecord::Base
end
def create_merge_request_diff
+ fetch_ref
+
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37435
Gitlab::GitalyClient.allow_n_plus_1_calls do
merge_request_diffs.create
@@ -462,6 +464,7 @@ class MergeRequest < ActiveRecord::Base
return unless open?
old_diff_refs = self.diff_refs
+
create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs
@@ -474,7 +477,7 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
- return unless unchecked?
+ return unless unchecked? && Gitlab::Database.read_write?
can_be_merged =
!broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
@@ -524,6 +527,14 @@ class MergeRequest < ActiveRecord::Base
true
end
+ def ff_merge_possible?
+ project.repository.ancestor?(target_branch_sha, diff_head_sha)
+ end
+
+ def should_be_rebased?
+ project.ff_merge_must_be_possible? && !ff_merge_possible?
+ end
+
def can_cancel_merge_when_pipeline_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
@@ -552,14 +563,20 @@ class MergeRequest < ActiveRecord::Base
commits_for_notes_limit = 100
commit_ids = commit_shas.take(commits_for_notes_limit)
- Note.where(
- "(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
- "((project_id = :source_project_id OR project_id = :target_project_id) AND noteable_type = 'Commit' AND commit_id IN (:commit_ids))",
- mr_id: id,
- commit_ids: commit_ids,
- target_project_id: target_project_id,
- source_project_id: source_project_id
- )
+ commit_notes = Note
+ .except(:order)
+ .where(project_id: [source_project_id, target_project_id])
+ .where(noteable_type: 'Commit', commit_id: commit_ids)
+
+ # We're using a UNION ALL here since this results in better performance
+ # compared to using OR statements. We're using UNION ALL since the queries
+ # used won't produce any duplicates (e.g. a note for a commit can't also be
+ # a note for an MR).
+ union = Gitlab::SQL::Union
+ .new([notes, commit_notes], remove_duplicates: false)
+ .to_sql
+
+ Note.from("(#{union}) #{Note.table_name}")
end
alias_method :discussion_notes, :related_notes
@@ -734,10 +751,9 @@ class MergeRequest < ActiveRecord::Base
end
def has_ci?
- has_ci_integration = source_project.try(:ci_service)
- uses_gitlab_ci = all_pipelines.any?
+ return false if has_no_commits?
- (has_ci_integration || uses_gitlab_ci) && commits.any?
+ !!(head_pipeline_id || all_pipelines.any? || source_project&.ci_service)
end
def branch_missing?
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 58050e1f438..faf0b95f842 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -55,7 +55,6 @@ class MergeRequestDiff < ActiveRecord::Base
end
def ensure_commit_shas
- merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
self.base_commit_sha ||= find_base_sha
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index e279d8dd8c5..4672881e220 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -139,7 +139,9 @@ class Namespace < ActiveRecord::Base
end
def find_fork_of(project)
- projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
+ return nil unless project.fork_network
+
+ project.fork_network.find_forks_in(projects).first
end
def lfs_enabled?
diff --git a/app/models/note.rb b/app/models/note.rb
index f44590e2144..ceded9f2aef 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -134,14 +134,22 @@ class Note < ActiveRecord::Base
Discussion.build(notes)
end
+ # Group diff discussions by line code or file path.
+ # It is not needed to group by line code when comment is
+ # on an image.
def grouped_diff_discussions(diff_refs = nil)
groups = {}
diff_notes.fresh.discussions.each do |discussion|
- line_code = discussion.line_code_in_diffs(diff_refs)
-
- if line_code
- discussions = groups[line_code] ||= []
+ group_key =
+ if discussion.on_image?
+ discussion.file_new_path
+ else
+ discussion.line_code_in_diffs(diff_refs)
+ end
+
+ if group_key
+ discussions = groups[group_key] ||= []
discussions << discussion
end
end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 1f9d712ef84..cfcb03138b7 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -17,6 +17,8 @@ class PersonalAccessToken < ActiveRecord::Base
validates :scopes, presence: true
validate :validate_scopes
+ after_initialize :set_default_scopes, if: :persisted?
+
def revoke!
update!(revoked: true)
end
@@ -32,4 +34,8 @@ class PersonalAccessToken < ActiveRecord::Base
errors.add :scopes, "can only contain available scopes"
end
end
+
+ def set_default_scopes
+ self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index f7221e4f3b2..57e91ab3b88 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -64,6 +64,7 @@ class Project < ActiveRecord::Base
# Storage specific hooks
after_initialize :use_hashed_storage
+ after_create :check_repository_absence!
after_create :ensure_storage_path_exists
after_save :ensure_storage_path_exists, if: :namespace_id_changed?
@@ -72,6 +73,7 @@ class Project < ActiveRecord::Base
attr_accessor :old_path_with_namespace
attr_accessor :template_name
attr_writer :pipeline_status
+ attr_accessor :skip_disk_validation
alias_attribute :title, :name
@@ -116,11 +118,20 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service
has_one :microsoft_teams_service
+ # TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
+ # TODO: replace these relations with the fork network versions
+
+ has_one :root_of_fork_network,
+ foreign_key: 'root_project_id',
+ inverse_of: :root_project,
+ class_name: 'ForkNetwork'
+ has_one :fork_network_member
+ has_one :fork_network, through: :fork_network_member
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id'
@@ -163,6 +174,7 @@ class Project < ActiveRecord::Base
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
has_one :project_feature, inverse_of: :project
has_one :statistics, class_name: 'ProjectStatistics'
+ has_one :cluster, class_name: 'Gcp::Cluster', inverse_of: :project
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
@@ -177,6 +189,7 @@ class Project < ActiveRecord::Base
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, class_name: 'Ci::Variable'
@@ -227,7 +240,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
- validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
+ validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
@@ -245,6 +258,9 @@ class Project < ActiveRecord::Base
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
+ scope :with_hashed_storage, -> { where('storage_version >= 1') }
+ scope :with_legacy_storage, -> { where(storage_version: [nil, 0]) }
+
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
@@ -808,7 +824,7 @@ class Project < ActiveRecord::Base
end
def cache_has_external_issue_tracker
- update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
+ update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) if Gitlab::Database.read_write?
end
def has_wiki?
@@ -828,7 +844,7 @@ class Project < ActiveRecord::Base
end
def cache_has_external_wiki
- update_column(:has_external_wiki, services.external_wikis.any?)
+ update_column(:has_external_wiki, services.external_wikis.any?) if Gitlab::Database.read_write?
end
def find_or_initialize_services(exceptions: [])
@@ -993,6 +1009,11 @@ class Project < ActiveRecord::Base
end
def forked?
+ return true if fork_network && fork_network.root_project != self
+
+ # TODO: Use only the above conditional using the `fork_network`
+ # This is the old conditional that looks at the `forked_project_link`, we
+ # fall back to this while we're migrating the new models
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
@@ -1015,24 +1036,29 @@ class Project < ActiveRecord::Base
end
# Check if repository already exists on disk
- def can_create_repository?
+ def check_repository_path_availability
+ return true if skip_disk_validation
return false unless repository_storage_path
expires_full_path_cache # we need to clear cache to validate renames correctly
- if gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
+ # Check if repository with same path already exists on disk we can
+ # skip this for the hashed storage because the path does not change
+ if legacy_storage? && repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk')
return false
end
true
+ rescue GRPC::Internal # if the path is too long
+ false
end
def create_repository(force: false)
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.add_repository(repository_storage_path, disk_path)
+ if gitlab_shell.add_repository(repository_storage, disk_path)
repository.after_create
true
else
@@ -1107,8 +1133,19 @@ class Project < ActiveRecord::Base
end
end
- def forked_from?(project)
- forked? && project == forked_from_project
+ def forked_from?(other_project)
+ forked? && forked_from_project == other_project
+ end
+
+ def in_fork_network_of?(other_project)
+ # TODO: Remove this in a next release when all fork_networks are populated
+ # This makes sure all MergeRequests remain valid while the projects don't
+ # have a fork_network yet.
+ return true if forked_from?(other_project)
+
+ return false if fork_network.nil? || other_project.fork_network.nil?
+
+ fork_network == other_project.fork_network
end
def origin_merge_requests
@@ -1550,18 +1587,72 @@ class Project < ActiveRecord::Base
end
def legacy_storage?
- self.storage_version.nil?
+ [nil, 0].include?(self.storage_version)
+ end
+
+ def hashed_storage?
+ self.storage_version && self.storage_version >= 1
end
def renamed?
persisted? && path_changed?
end
+ def merge_method
+ if self.merge_requests_ff_only_enabled
+ :ff
+ elsif self.merge_requests_rebase_enabled
+ :rebase_merge
+ else
+ :merge
+ end
+ end
+
+ def merge_method=(method)
+ case method.to_s
+ when "ff"
+ self.merge_requests_ff_only_enabled = true
+ self.merge_requests_rebase_enabled = true
+ when "rebase_merge"
+ self.merge_requests_ff_only_enabled = false
+ self.merge_requests_rebase_enabled = true
+ when "merge"
+ self.merge_requests_ff_only_enabled = false
+ self.merge_requests_rebase_enabled = false
+ end
+ end
+
+ def ff_merge_must_be_possible?
+ self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
+ end
+
+ def migrate_to_hashed_storage!
+ return if hashed_storage?
+
+ update!(repository_read_only: true)
+
+ if repo_reference_count > 0 || wiki_reference_count > 0
+ ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
+ else
+ ProjectMigrateHashedStorageWorker.perform_async(id)
+ end
+ end
+
+ def storage_version=(value)
+ super
+
+ @storage = nil if storage_version_changed?
+ end
+
+ def gl_repository(is_wiki:)
+ Gitlab::GlRepository.gl_repository(self, is_wiki)
+ end
+
private
def storage
@storage ||=
- if self.storage_version && self.storage_version >= 1
+ if hashed_storage?
Storage::HashedProject.new(self)
else
Storage::LegacyProject.new(self)
@@ -1574,6 +1665,27 @@ class Project < ActiveRecord::Base
end
end
+ def repo_reference_count
+ Gitlab::ReferenceCounter.new(gl_repository(is_wiki: false)).value
+ end
+
+ def wiki_reference_count
+ Gitlab::ReferenceCounter.new(gl_repository(is_wiki: true)).value
+ end
+
+ def check_repository_absence!
+ return if skip_disk_validation
+
+ if repository_storage_path.blank? || repository_with_same_path_already_exists?
+ errors.add(:base, 'There is already a repository with that name on disk')
+ throw :abort
+ end
+ end
+
+ def repository_with_same_path_already_exists?
+ gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
+ end
+
# set last_activity_at to the same as created_at
def set_last_activity_at
update_column(:last_activity_at, self.created_at)
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index e2ad586aea7..22a65b5145e 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -3,6 +3,7 @@ require 'slack-notifier'
module ChatMessage
class BaseMessage
attr_reader :markdown
+ attr_reader :user_full_name
attr_reader :user_name
attr_reader :user_avatar
attr_reader :project_name
@@ -12,10 +13,19 @@ module ChatMessage
@markdown = params[:markdown] || false
@project_name = params.dig(:project, :path_with_namespace) || params[:project_name]
@project_url = params.dig(:project, :web_url) || params[:project_url]
+ @user_full_name = params.dig(:user, :name) || params[:user_full_name]
@user_name = params.dig(:user, :username) || params[:user_name]
@user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar]
end
+ def user_combined_name
+ if user_full_name.present?
+ "#{user_full_name} (#{user_name})"
+ else
+ user_name
+ end
+ end
+
def pretext
return message if markdown
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 4b9a2b1e1f3..1327b075858 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -29,7 +29,7 @@ module ChatMessage
def activity
{
- title: "Issue #{state} by #{user_name}",
+ title: "Issue #{state} by #{user_combined_name}",
subtitle: "in #{project_link}",
text: issue_link,
image: user_avatar
@@ -40,9 +40,9 @@ module ChatMessage
def message
if state == 'opened'
- "[#{project_link}] Issue #{state} by #{user_name}"
+ "[#{project_link}] Issue #{state} by #{user_combined_name}"
else
- "[#{project_link}] Issue #{issue_link} #{state} by #{user_name}"
+ "[#{project_link}] Issue #{issue_link} #{state} by #{user_combined_name}"
end
end
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index 7d0de81cdf0..f412b6833d9 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -24,7 +24,7 @@ module ChatMessage
def activity
{
- title: "Merge Request #{state} by #{user_name}",
+ title: "Merge Request #{state} by #{user_combined_name}",
subtitle: "in #{project_link}",
text: merge_request_link,
image: user_avatar
@@ -46,7 +46,7 @@ module ChatMessage
end
def merge_request_message
- "#{user_name} #{state} #{merge_request_link} in #{project_link}: #{title}"
+ "#{user_combined_name} #{state} #{merge_request_link} in #{project_link}: #{title}"
end
def merge_request_link
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index 2da4c244229..7f9486132e6 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -32,7 +32,7 @@ module ChatMessage
def activity
{
- title: "#{user_name} #{link('commented on ' + target, note_url)}",
+ title: "#{user_combined_name} #{link('commented on ' + target, note_url)}",
subtitle: "in #{project_link}",
text: formatted_title,
image: user_avatar
@@ -42,7 +42,7 @@ module ChatMessage
private
def message
- "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*"
+ "#{user_combined_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*"
end
def format_title(title)
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index d63d4ec2b12..2135122278a 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -9,7 +9,7 @@ module ChatMessage
def initialize(data)
super
- @user_name = data.dig(:user, :name) || 'API'
+ @user_name = data.dig(:user, :username) || 'API'
pipeline_attributes = data[:object_attributes]
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
@@ -35,7 +35,7 @@ module ChatMessage
def activity
{
- title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_name} #{humanized_status}",
+ title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status}",
subtitle: "in #{project_link}",
text: "in #{pretty_duration(duration)}",
image: user_avatar || ''
@@ -45,7 +45,7 @@ module ChatMessage
private
def message
- "#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_name} #{humanized_status} in #{pretty_duration(duration)}"
+ "#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status} in #{pretty_duration(duration)}"
end
def humanized_status
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index c52dd6ef8ef..8d599c5f116 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -33,7 +33,7 @@ module ChatMessage
end
{
- title: "#{user_name} #{action} #{ref_type}",
+ title: "#{user_combined_name} #{action} #{ref_type}",
subtitle: "in #{project_link}",
text: compare_link,
image: user_avatar
@@ -57,15 +57,15 @@ module ChatMessage
end
def new_branch_message
- "#{user_name} pushed new #{ref_type} #{branch_link} to #{project_link}"
+ "#{user_combined_name} pushed new #{ref_type} #{branch_link} to #{project_link}"
end
def removed_branch_message
- "#{user_name} removed #{ref_type} #{ref} from #{project_link}"
+ "#{user_combined_name} removed #{ref_type} #{ref} from #{project_link}"
end
def push_message
- "#{user_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})"
+ "#{user_combined_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})"
end
def commit_messages
diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb
index a139a8ee727..d84b80f2de2 100644
--- a/app/models/project_services/chat_message/wiki_page_message.rb
+++ b/app/models/project_services/chat_message/wiki_page_message.rb
@@ -31,7 +31,7 @@ module ChatMessage
def activity
{
- title: "#{user_name} #{action} #{wiki_page_link}",
+ title: "#{user_combined_name} #{action} #{wiki_page_link}",
subtitle: "in #{project_link}",
text: title,
image: user_avatar
@@ -41,7 +41,7 @@ module ChatMessage
private
def message
- "#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
+ "#{user_combined_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
end
def description_message
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 698fdf7a20c..bb7be29ef66 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -54,12 +54,15 @@ class ProjectWiki
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('')
end
- # Returns the Gollum::Wiki object.
+ # Returns the Gitlab::Git::Wiki object.
def wiki
@wiki ||= begin
- Gollum::Wiki.new(path_to_repo)
- rescue Rugged::OSError
- create_repo!
+ gl_repository = Gitlab::GlRepository.gl_repository(project, true)
+ raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository)
+
+ create_repo!(raw_repository) unless raw_repository.exists?
+
+ Gitlab::Git::Wiki.new(raw_repository)
end
end
@@ -86,20 +89,14 @@ class ProjectWiki
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil)
page_title, page_dir = page_title_and_dir(title)
- if page = wiki.page(page_title, version, page_dir)
+
+ if page = wiki.page(title: page_title, version: version, dir: page_dir)
WikiPage.new(self, page, true)
- else
- nil
end
end
- def find_file(name, version = nil, try_on_disk = true)
- version = wiki.ref if version.nil? # Gollum::Wiki#file ?
- if wiki_file = wiki.file(name, version, try_on_disk)
- wiki_file
- else
- nil
- end
+ def find_file(name, version = nil)
+ wiki.file(name, version)
end
def create_page(title, content, format = :markdown, message = nil)
@@ -108,7 +105,7 @@ class ProjectWiki
wiki.write_page(title, format.to_sym, content, commit)
update_project_activity
- rescue Gollum::DuplicatePageError => e
+ rescue Gitlab::Git::Wiki::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}"
return false
end
@@ -116,13 +113,13 @@ class ProjectWiki
def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title)
- wiki.update_page(page, title || page.name, format.to_sym, content, commit)
+ wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
update_project_activity
end
def delete_page(page, message = nil)
- wiki.delete_page(page, commit_details(:deleted, message, page.title))
+ wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity
end
@@ -145,20 +142,8 @@ class ProjectWiki
wiki.class.default_ref
end
- def create_repo!
- if init_repo(disk_path)
- wiki = Gollum::Wiki.new(path_to_repo)
- else
- raise CouldNotCreateWikiError
- end
-
- repository.after_create
-
- wiki
- end
-
def ensure_repository
- create_repo! unless repository_exists?
+ raise CouldNotCreateWikiError unless wiki.repository_exists?
end
def hook_attrs
@@ -173,24 +158,24 @@ class ProjectWiki
private
- def init_repo(disk_path)
- gitlab_shell.add_repository(project.repository_storage_path, disk_path)
+ def create_repo!(raw_repository)
+ gitlab_shell.add_repository(project.repository_storage, disk_path)
+
+ raise CouldNotCreateWikiError unless raw_repository.exists?
+
+ repository.after_create
end
def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title)
- { email: @user.email, name: @user.name, message: commit_message }
+ Gitlab::Git::Wiki::CommitDetails.new(@user.name, @user.email, commit_message)
end
def default_message(action, title)
"#{@user.username} #{action} page: #{title}"
end
- def path_to_repo
- @path_to_repo ||= File.join(project.repository_storage_path, "#{disk_path}.git")
- end
-
def update_project_activity
@project.touch(:last_activity_at, :last_repository_updated_at)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 90cede9d3d4..d725c65081d 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -34,7 +34,10 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
- tag_count avatar exists? empty? root_ref).freeze
+ tag_count avatar exists? empty? root_ref has_visible_content?).freeze
+
+ # Methods that use cache_method but only memoize the value
+ MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
@@ -91,12 +94,6 @@ class Repository
)
end
- # we need to have this method here because it is not cached in ::Git and
- # the method is called multiple times for every request
- def has_visible_content?
- branch_count > 0
- end
-
def inspect
"#<#{self.class.name}:#{@disk_path}>"
end
@@ -275,7 +272,7 @@ class Repository
end
def expire_branches_cache
- expire_method_caches(%i(branch_names branch_count))
+ expire_method_caches(%i(branch_names branch_count has_visible_content?))
@local_branches = nil
@branch_exists_memo = nil
end
@@ -346,7 +343,7 @@ class Repository
def expire_emptiness_caches
return unless empty?
- expire_method_caches(%i(empty?))
+ expire_method_caches(%i(empty? has_visible_content?))
end
def lookup_cache
@@ -489,13 +486,7 @@ class Repository
def exists?
return false unless full_path
- Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
- if enabled
- raw_repository.exists?
- else
- refs_directory_exists?
- end
- end
+ raw_repository.exists?
end
cache_method :exists?
@@ -529,13 +520,17 @@ class Repository
delegate :tag_names, to: :raw_repository
cache_method :tag_names, fallback: []
- delegate :branch_count, :tag_count, to: :raw_repository
+ delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
cache_method :tag_count, fallback: 0
+ cache_method :has_visible_content?, fallback: false
def avatar
- if tree = file_on_head(:avatar)
- tree.path
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ if tree = file_on_head(:avatar)
+ tree.path
+ end
end
end
cache_method :avatar
@@ -855,6 +850,25 @@ class Repository
end
end
+ def ff_merge(user, source, target_branch, merge_request: nil)
+ our_commit = rugged.branches[target_branch].target
+ their_commit =
+ if source.is_a?(Gitlab::Git::Commit)
+ source.raw_commit
+ else
+ rugged.lookup(source)
+ end
+
+ raise 'Invalid merge target' if our_commit.nil?
+ raise 'Invalid merge source' if their_commit.nil?
+
+ with_branch(user, target_branch) do |start_commit|
+ merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)
+
+ their_commit.oid
+ end
+ end
+
def revert(
user, commit, branch_name, message,
start_branch_name: nil, start_project: project)
@@ -975,7 +989,7 @@ class Repository
end
def create_ref(ref, ref_path)
- fetch_ref(path_to_repo, ref, ref_path)
+ raw_repository.write_ref(ref_path, ref)
end
def ls_files(ref)
@@ -1060,12 +1074,6 @@ class Repository
blob.data
end
- def refs_directory_exists?
- circuit_breaker.perform do
- File.exist?(File.join(path_to_repo, 'refs'))
- end
- end
-
def cache
# TODO: should we use UUIDs here? We could move repositories without clearing this cache
@cache ||= RepositoryCache.new(full_path, @project.id)
@@ -1117,10 +1125,6 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, false))
end
- def circuit_breaker
- @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage)
- end
-
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index fae1b64961a..f025f40994e 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -4,6 +4,7 @@ module Storage
delegate :gitlab_shell, :repository_storage_path, to: :project
ROOT_PATH_PREFIX = '@hashed'.freeze
+ STORAGE_VERSION = 1
def initialize(project)
@project = project
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 0b33e45473b..1f9f8d7286b 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -2,7 +2,7 @@ class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
- opened closed merged duplicate
+ opened closed merged duplicate locked unlocked
outdated
].freeze
diff --git a/app/models/user.rb b/app/models/user.rb
index 09c9b3250eb..533a776bc65 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -60,7 +60,7 @@ class User < ActiveRecord::Base
lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
return unless lease.try_obtain
- Users::UpdateService.new(self).execute(validate: false)
+ Users::UpdateService.new(self, user: self).execute(validate: false)
end
attr_accessor :force_random_password
@@ -130,6 +130,8 @@ class User < ActiveRecord::Base
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
+ has_many :custom_attributes, class_name: 'UserCustomAttribute'
+
#
# Validations
#
@@ -161,15 +163,16 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: :public_email_changed?
-
- after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
+ before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
after_save :ensure_namespace_correct
+ after_destroy :post_destroy_hook
+ after_commit :update_emails_with_primary_email, on: :update, if: -> { previous_changes.key?('email') }
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
+
after_initialize :set_projects_limit
- after_destroy :post_destroy_hook
# User's Layout preference
enum layout: [:fixed, :fluid]
@@ -456,6 +459,14 @@ class User < ActiveRecord::Base
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
+ def remember_me!
+ super if ::Gitlab::Database.read_write?
+ end
+
+ def forget_me!
+ super if ::Gitlab::Database.read_write?
+ end
+
def disable_two_factor!
transaction do
update_attributes(
@@ -523,12 +534,24 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ # see if the new email is already a verified secondary email
+ def check_for_verified_email
+ skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
+ end
+
+ # Note: the use of the Emails services will cause `saves` on the user object, running
+ # through the callbacks again and can have side effects, such as the `previous_changes`
+ # hash and `_was` variables getting munged.
+ # By using an `after_commit` instead of `after_update`, we avoid the recursive callback
+ # scenario, though it then requires us to use the `previous_changes` hash
def update_emails_with_primary_email
+ previous_email = previous_changes[:email][0] # grab this before the DestroyService is called
primary_email_record = emails.find_by(email: email)
- if primary_email_record
- Emails::DestroyService.new(self, email: email).execute
- Emails::CreateService.new(self, email: email_was).execute
- end
+ Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
+
+ # the original primary email was confirmed, and we want that to carry over. We don't
+ # have access to the original confirmation values at this point, so just set confirmed_at
+ Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: confirmed_at)
end
def update_invalid_gpg_signatures
@@ -639,6 +662,10 @@ class User < ActiveRecord::Base
Ability.allowed?(self, action, subject)
end
+ def confirm_deletion_with_password?
+ !password_automatically_set? && allow_password_authentication?
+ end
+
def first_name
name.split.first unless name.blank?
end
@@ -678,19 +705,15 @@ class User < ActiveRecord::Base
end
def fork_of(project)
- links = ForkedProjectLink.where(
- forked_from_project_id: project,
- forked_to_project_id: personal_projects.unscope(:order)
- )
- if links.any?
- links.first.forked_to_project
- else
- nil
- end
+ namespace.find_fork_of(project)
end
def ldap_user?
- identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
+ if identities.loaded?
+ identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
+ else
+ identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
+ end
end
def ldap_identity
@@ -810,6 +833,10 @@ class User < ActiveRecord::Base
avatar_path(args) || GravatarService.new.execute(email, size, scale, username: username)
end
+ def primary_email_verified?
+ confirmed? && !temp_oauth_email?
+ end
+
def all_emails
all_emails = []
all_emails << email unless temp_oauth_email?
@@ -817,6 +844,18 @@ class User < ActiveRecord::Base
all_emails
end
+ def verified_emails
+ verified_emails = []
+ verified_emails << email if primary_email_verified?
+ verified_emails.concat(emails.confirmed.pluck(:email))
+ verified_emails
+ end
+
+ def verified_email?(check_email)
+ downcased = check_email.downcase
+ email == downcased ? primary_email_verified? : emails.confirmed.where(email: downcased).exists?
+ end
+
def hook_attrs
{
name: name,
@@ -1000,7 +1039,7 @@ class User < ActiveRecord::Base
if attempts_exceeded?
lock_access! unless access_locked?
else
- Users::UpdateService.new(self).execute(validate: false)
+ Users::UpdateService.new(self, user: self).execute(validate: false)
end
end
@@ -1041,10 +1080,6 @@ class User < ActiveRecord::Base
ensure_rss_token!
end
- def verified_email?(email)
- self.email == email
- end
-
def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email
@@ -1061,6 +1096,12 @@ class User < ActiveRecord::Base
user_synced_attributes_metadata&.read_only?(attribute)
end
+ # override, from Devise
+ def lock_access!
+ Gitlab::AppLogger.info("Account Locked: username=#{username}")
+ super
+ end
+
protected
# override, from Devise::Validatable
@@ -1186,7 +1227,7 @@ class User < ActiveRecord::Base
&creation_block
)
- Users::UpdateService.new(user).execute(validate: false)
+ Users::UpdateService.new(user, user: user).execute(validate: false)
user
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb
new file mode 100644
index 00000000000..eff25b31f9b
--- /dev/null
+++ b/app/models/user_custom_attribute.rb
@@ -0,0 +1,6 @@
+class UserCustomAttribute < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user_id, :key, :value, presence: true
+ validates :key, uniqueness: { scope: [:user_id] }
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index f2315bb3dbb..5f710961f95 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -50,7 +50,7 @@ class WikiPage
# The Gitlab ProjectWiki instance.
attr_reader :wiki
- # The raw Gollum::Page instance.
+ # The raw Gitlab::Git::WikiPage instance.
attr_reader :page
# The attributes Hash used for storing and validating
@@ -75,7 +75,7 @@ class WikiPage
if @attributes[:slug].present?
@attributes[:slug]
else
- wiki.wiki.preview_page(title, '', format).url_path
+ wiki.wiki.preview_slug(title, format)
end
end
@@ -131,7 +131,7 @@ class WikiPage
def versions
return [] unless persisted?
- @page.versions
+ wiki.wiki.page_versions(@page.path)
end
def commit
@@ -264,8 +264,8 @@ class WikiPage
end
page_title, page_dir = wiki.page_title_and_dir(page_details)
- gollum_wiki = wiki.wiki
- @page = gollum_wiki.paged(page_title, page_dir)
+ gitlab_git_wiki = wiki.wiki
+ @page = gitlab_git_wiki.page(title: page_title, dir: page_dir)
set_attributes
@persisted = errors.blank?
diff --git a/app/policies/gcp/cluster_policy.rb b/app/policies/gcp/cluster_policy.rb
new file mode 100644
index 00000000000..e77173ea6e1
--- /dev/null
+++ b/app/policies/gcp/cluster_policy.rb
@@ -0,0 +1,12 @@
+module Gcp
+ class ClusterPolicy < BasePolicy
+ alias_method :cluster, :subject
+
+ delegate { @subject.project }
+
+ rule { can?(:master_access) }.policy do
+ enable :update_cluster
+ enable :admin_cluster
+ end
+ end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 1be7bbe9953..64e550d19d0 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -11,6 +11,8 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
+ condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
+
rule { anonymous }.policy do
prevent :log_in
prevent :access_api
@@ -40,6 +42,10 @@ class GlobalPolicy < BasePolicy
enable :create_group
end
+ rule { can_create_fork }.policy do
+ enable :create_fork
+ end
+
rule { access_locked }.policy do
prevent :log_in
end
@@ -47,4 +53,9 @@ class GlobalPolicy < BasePolicy
rule { ~(anonymous & restricted_public_level) }.policy do
enable :read_users_list
end
+
+ rule { admin }.policy do
+ enable :read_custom_attribute
+ enable :update_custom_attribute
+ end
end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index daf6fa9e18a..f0aa16d2ecf 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -1,6 +1,10 @@
class IssuablePolicy < BasePolicy
delegate { @subject.project }
+ condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
+
+ condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) }
+
desc "User is the assignee or author"
condition(:assignee_or_author) do
@user && @subject.assignee_or_author?(@user)
@@ -12,4 +16,12 @@ class IssuablePolicy < BasePolicy
enable :read_merge_request
enable :update_merge_request
end
+
+ rule { locked & ~is_project_member }.policy do
+ prevent :create_note
+ prevent :update_note
+ prevent :admin_note
+ prevent :resolve_note
+ prevent :edit_note
+ end
end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index 85b67f0a237..92213f0155e 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -1,10 +1,14 @@
class NamespacePolicy < BasePolicy
rule { anonymous }.prevent_all
+ condition(:personal_project, scope: :subject) { @subject.kind == 'user' }
+ condition(:can_create_personal_project, scope: :user) { @user.can_create_project? }
condition(:owner) { @subject.owner == @user }
rule { owner | admin }.policy do
enable :create_projects
enable :admin_namespace
end
+
+ rule { personal_project & ~can_create_personal_project }.prevent :create_projects
end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index 20cd51cfb99..d4cb5a77e63 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,5 +1,6 @@
class NotePolicy < BasePolicy
delegate { @subject.project }
+ delegate { @subject.noteable if @subject.noteable.lockable? }
condition(:is_author) { @user && @subject.author == @user }
condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
@@ -8,6 +9,7 @@ class NotePolicy < BasePolicy
condition(:editable, scope: :subject) { @subject.editable? }
rule { ~editable | anonymous }.prevent :edit_note
+
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index b7b5bd34189..f599eab42f2 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -193,6 +193,8 @@ class ProjectPolicy < BasePolicy
enable :admin_pages
enable :read_pages
enable :update_pages
+ enable :read_cluster
+ enable :create_cluster
end
rule { can?(:public_user_access) }.policy do
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index a542bdd8295..099b4720fb6 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -1,7 +1,18 @@
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
+ FAILURE_REASONS = {
+ config_error: 'CI/CD YAML configuration error!'
+ }.freeze
+
presents :pipeline
+ def failure_reason
+ return unless pipeline.failure_reason?
+
+ FAILURE_REASONS[pipeline.failure_reason.to_sym] ||
+ pipeline.failure_reason
+ end
+
def status_title
if auto_canceled?
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
diff --git a/app/presenters/gcp/cluster_presenter.rb b/app/presenters/gcp/cluster_presenter.rb
new file mode 100644
index 00000000000..f7908f92a37
--- /dev/null
+++ b/app/presenters/gcp/cluster_presenter.rb
@@ -0,0 +1,9 @@
+module Gcp
+ class ClusterPresenter < Gitlab::View::Presenter::Delegated
+ presents :cluster
+
+ def gke_cluster_url
+ "https://console.cloud.google.com/kubernetes/clusters/details/#{gcp_cluster_zone}/#{gcp_cluster_name}"
+ end
+ end
+end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 2df84e58575..a25882cbb62 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -31,7 +31,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def remove_wip_path
- if can?(current_user, :update_merge_request, merge_request.project)
+ if work_in_progress? && can?(current_user, :update_merge_request, merge_request.project)
remove_wip_project_merge_request_path(project, merge_request)
end
end
diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb
new file mode 100644
index 00000000000..08a113c4d8a
--- /dev/null
+++ b/app/serializers/cluster_entity.rb
@@ -0,0 +1,6 @@
+class ClusterEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :status_name, as: :status
+ expose :status_reason
+end
diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb
new file mode 100644
index 00000000000..2c87202a105
--- /dev/null
+++ b/app/serializers/cluster_serializer.rb
@@ -0,0 +1,7 @@
+class ClusterSerializer < BaseSerializer
+ entity ClusterEntity
+
+ def represent_status(resource)
+ represent(resource, { only: [:status, :status_reason] })
+ end
+end
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index e4e9d8ef90a..c8dd98cc04d 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -1,4 +1,4 @@
-class CommitEntity < API::Entities::RepoCommit
+class CommitEntity < API::Entities::Commit
include RequestAwareEntity
expose :author, using: UserEntity
diff --git a/app/serializers/container_repositories_serializer.rb b/app/serializers/container_repositories_serializer.rb
new file mode 100644
index 00000000000..56dc70b5687
--- /dev/null
+++ b/app/serializers/container_repositories_serializer.rb
@@ -0,0 +1,3 @@
+class ContainerRepositoriesSerializer < BaseSerializer
+ entity ContainerRepositoryEntity
+end
diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb
new file mode 100644
index 00000000000..1103cf30a07
--- /dev/null
+++ b/app/serializers/container_repository_entity.rb
@@ -0,0 +1,25 @@
+class ContainerRepositoryEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id, :path, :location
+
+ expose :tags_path do |repository|
+ project_registry_repository_tags_path(project, repository, format: :json)
+ end
+
+ expose :destroy_path, if: -> (*) { can_destroy? } do |repository|
+ project_container_registry_path(project, repository, format: :json)
+ end
+
+ private
+
+ alias_method :repository, :object
+
+ def project
+ request.project
+ end
+
+ def can_destroy?
+ can?(request.current_user, :update_container_image, project)
+ end
+end
diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb
new file mode 100644
index 00000000000..ec1fc349586
--- /dev/null
+++ b/app/serializers/container_tag_entity.rb
@@ -0,0 +1,23 @@
+class ContainerTagEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :name, :location, :revision, :total_size, :created_at
+
+ expose :destroy_path, if: -> (*) { can_destroy? } do |tag|
+ project_registry_repository_tag_path(project, tag.repository, tag.name, format: :json)
+ end
+
+ private
+
+ alias_method :tag, :object
+
+ def project
+ request.project
+ end
+
+ def can_destroy?
+ # TODO: We check permission against @project, not tag,
+ # as tag is no AR object that is attached to project
+ can?(request.current_user, :update_container_image, project)
+ end
+end
diff --git a/app/serializers/container_tags_serializer.rb b/app/serializers/container_tags_serializer.rb
new file mode 100644
index 00000000000..6ff3adff135
--- /dev/null
+++ b/app/serializers/container_tags_serializer.rb
@@ -0,0 +1,17 @@
+class ContainerTagsSerializer < BaseSerializer
+ entity ContainerTagEntity
+
+ def with_pagination(request, response)
+ tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
+ end
+
+ def paginated?
+ @paginator.present?
+ end
+
+ def represent(resource, opts = {})
+ resource = @paginator.paginate(resource) if paginated?
+
+ super(resource, opts)
+ end
+end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 0d6feb78173..10d3ad0214b 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -3,6 +3,7 @@ class IssueEntity < IssuableEntity
expose :branch_name
expose :confidential
+ expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic
expose :due_date
expose :moved_to_id
@@ -14,7 +15,7 @@ class IssueEntity < IssuableEntity
expose :current_user do
expose :can_create_note do |issue|
- can?(request.current_user, :create_note, issue.project)
+ can?(request.current_user, :create_note, issue)
end
expose :can_update do |issue|
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 07650ce6f20..297a459e394 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -13,12 +13,16 @@ class MergeRequestEntity < IssuableEntity
expose :target_branch
expose :target_project_id
+ expose :should_be_rebased?, as: :should_be_rebased
+ expose :ff_only_enabled do |merge_request|
+ merge_request.project.merge_requests_ff_only_enabled
+ end
+
# Events
expose :merge_event, using: EventEntity
expose :closed_event, using: EventEntity
# User entities
- expose :author, using: UserEntity
expose :merge_user, using: UserEntity
# Diff sha's
@@ -26,7 +30,6 @@ class MergeRequestEntity < IssuableEntity
merge_request.diff_head_sha if merge_request.diff_head_commit
end
- expose :merge_commit_sha
expose :merge_commit_message
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
@@ -39,6 +42,7 @@ class MergeRequestEntity < IssuableEntity
expose :commits_count
expose :cannot_be_merged?, as: :has_conflicts
expose :can_be_merged?, as: :can_be_merged
+ expose :mergeable?, as: :mergeable
expose :remove_source_branch?, as: :remove_source_branch
expose :project_archived do |merge_request|
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 357fc71f877..6457294b285 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -20,6 +20,7 @@ class PipelineEntity < Grape::Entity
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
+ expose :failure_reason?, as: :failure_reason
end
expose :details do
@@ -44,6 +45,11 @@ class PipelineEntity < Grape::Entity
end
expose :commit, using: CommitEntity
+ expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
+
+ expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline|
+ pipeline.present.failure_reason
+ end
expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_project_pipeline_path(pipeline.project, pipeline)
@@ -53,8 +59,6 @@ class PipelineEntity < Grape::Entity
cancel_project_pipeline_path(pipeline.project, pipeline)
end
- expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
-
private
alias_method :pipeline, :object
diff --git a/app/services/ci/create_cluster_service.rb b/app/services/ci/create_cluster_service.rb
new file mode 100644
index 00000000000..f7ee0e468e2
--- /dev/null
+++ b/app/services/ci/create_cluster_service.rb
@@ -0,0 +1,15 @@
+module Ci
+ class CreateClusterService < BaseService
+ def execute(access_token)
+ params['gcp_machine_type'] ||= GoogleApi::CloudPlatform::Client::DEFAULT_MACHINE_TYPE
+
+ cluster_params =
+ params.merge(user: current_user,
+ gcp_token: access_token)
+
+ project.create_cluster(cluster_params).tap do |cluster|
+ ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
+ end
+ end
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index d20de9b16a4..31a712ccc1b 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -2,110 +2,55 @@ module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
- def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil)
+ SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
+ Gitlab::Ci::Pipeline::Chain::Validate::Repository,
+ Gitlab::Ci::Pipeline::Chain::Validate::Config,
+ Gitlab::Ci::Pipeline::Chain::Skip,
+ Gitlab::Ci::Pipeline::Chain::Create].freeze
+
+ def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new(
source: source,
project: project,
ref: ref,
sha: sha,
before_sha: before_sha,
- tag: tag?,
+ tag: tag_exists?,
trigger_requests: Array(trigger_request),
user: current_user,
pipeline_schedule: schedule,
protected: project.protected_for?(ref)
)
- result = validate_project_and_git_items ||
- validate_pipeline(ignore_skip_ci: ignore_skip_ci,
- save_on_errors: save_on_errors)
+ command = OpenStruct.new(ignore_skip_ci: ignore_skip_ci,
+ save_incompleted: save_on_errors,
+ seeds_block: block,
+ project: project,
+ current_user: current_user)
- return result if result
+ sequence = Gitlab::Ci::Pipeline::Chain::Sequence
+ .new(pipeline, command, SEQUENCE)
- begin
- Ci::Pipeline.transaction do
- pipeline.save!
+ sequence.build! do |pipeline, sequence|
+ update_merge_requests_head_pipeline if pipeline.persisted?
- yield(pipeline) if block_given?
+ if sequence.complete?
+ cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
+ pipeline_created_counter.increment(source: source)
- Ci::CreatePipelineStagesService
- .new(project, current_user)
- .execute(pipeline)
+ pipeline.process!
end
- rescue ActiveRecord::RecordInvalid => e
- return error("Failed to persist the pipeline: #{e}")
end
-
- update_merge_requests_head_pipeline
-
- cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
-
- pipeline_created_counter.increment(source: source)
-
- pipeline.tap(&:process!)
end
private
- def validate_project_and_git_items
- unless project.builds_enabled?
- return error('Pipeline is disabled')
- end
-
- unless allowed_to_trigger_pipeline?
- if can?(current_user, :create_pipeline, project)
- return error("Insufficient permissions for protected ref '#{ref}'")
- else
- return error('Insufficient permissions to create a new pipeline')
- end
- end
-
- unless branch? || tag?
- return error('Reference not found')
- end
-
- unless commit
- return error('Commit not found')
- end
- end
-
- def validate_pipeline(ignore_skip_ci:, save_on_errors:)
- unless pipeline.config_processor
- unless pipeline.ci_yaml_file
- return error("Missing #{pipeline.ci_yaml_file_path} file")
- end
- return error(pipeline.yaml_errors, save: save_on_errors)
- end
-
- if !ignore_skip_ci && skip_ci?
- pipeline.skip if save_on_errors
- return pipeline
- end
-
- unless pipeline.has_stage_seeds?
- return error('No stages / jobs for this pipeline.')
- end
- end
-
- def allowed_to_trigger_pipeline?
- if current_user
- allowed_to_create?
- else # legacy triggers don't have a corresponding user
- !project.protected_for?(ref)
- end
+ def commit
+ @commit ||= project.commit(origin_sha || origin_ref)
end
- def allowed_to_create?
- return unless can?(current_user, :create_pipeline, project)
-
- access = Gitlab::UserAccess.new(current_user, project: project)
- if branch?
- access.can_update_branch?(ref)
- elsif tag?
- access.can_create_tag?(ref)
- else
- true # Allow it for now and we'll reject when we check ref existence
- end
+ def sha
+ commit.try(:id)
end
def update_merge_requests_head_pipeline
@@ -115,11 +60,6 @@ module Ci
.update_all(head_pipeline_id: @pipeline.id)
end
- def skip_ci?
- return false unless pipeline.git_commit_message
- pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
- end
-
def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable|
@@ -136,14 +76,6 @@ module Ci
.created_or_pending
end
- def commit
- @commit ||= project.commit(origin_sha || origin_ref)
- end
-
- def sha
- commit.try(:id)
- end
-
def before_sha
params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
end
@@ -156,41 +88,17 @@ module Ci
params[:ref]
end
- def branch?
- return @is_branch if defined?(@is_branch)
-
- @is_branch =
- project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
- end
-
- def tag?
- return @is_tag if defined?(@is_tag)
-
- @is_tag =
- project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
+ def tag_exists?
+ project.repository.tag_exists?(ref)
end
def ref
@ref ||= Gitlab::Git.ref_name(origin_ref)
end
- def valid_sha?
- origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
- end
-
- def error(message, save: false)
- pipeline.tap do
- pipeline.errors.add(:base, message)
-
- if save
- pipeline.drop
- update_merge_requests_head_pipeline
- end
- end
- end
-
def pipeline_created_counter
- @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created")
+ @pipeline_created_counter ||= Gitlab::Metrics
+ .counter(:pipelines_created_total, "Counter of pipelines created")
end
end
end
diff --git a/app/services/ci/extract_sections_from_build_trace_service.rb b/app/services/ci/extract_sections_from_build_trace_service.rb
new file mode 100644
index 00000000000..75f9e0f897d
--- /dev/null
+++ b/app/services/ci/extract_sections_from_build_trace_service.rb
@@ -0,0 +1,30 @@
+module Ci
+ class ExtractSectionsFromBuildTraceService < BaseService
+ def execute(build)
+ return false unless build.trace_sections.empty?
+
+ Gitlab::Database.bulk_insert(BuildTraceSection.table_name, extract_sections(build))
+ true
+ end
+
+ private
+
+ def find_or_create_name(name)
+ project.build_trace_section_names.find_or_create_by!(name: name)
+ rescue ActiveRecord::RecordInvalid
+ project.build_trace_section_names.find_by!(name: name)
+ end
+
+ def extract_sections(build)
+ build.trace.extract_sections.map do |attr|
+ name = attr.delete(:name)
+ name_record = find_or_create_name(name)
+
+ attr.merge(
+ build_id: build.id,
+ project_id: project.id,
+ section_name_id: name_record.id)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/fetch_gcp_operation_service.rb b/app/services/ci/fetch_gcp_operation_service.rb
new file mode 100644
index 00000000000..0b68e4d6ea9
--- /dev/null
+++ b/app/services/ci/fetch_gcp_operation_service.rb
@@ -0,0 +1,17 @@
+module Ci
+ class FetchGcpOperationService
+ def execute(cluster)
+ api_client =
+ GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
+
+ operation = api_client.projects_zones_operations(
+ cluster.gcp_project_id,
+ cluster.gcp_cluster_zone,
+ cluster.gcp_operation_id)
+
+ yield(operation) if block_given?
+ rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
+ return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ end
+ end
+end
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
new file mode 100644
index 00000000000..44da87cb00c
--- /dev/null
+++ b/app/services/ci/fetch_kubernetes_token_service.rb
@@ -0,0 +1,72 @@
+##
+# TODO:
+# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
+# We should dry up those classes not to repeat the same code.
+# Maybe we should have a special facility (e.g. lib/kubernetes_api) to maintain all Kubernetes API caller.
+module Ci
+ class FetchKubernetesTokenService
+ attr_reader :api_url, :ca_pem, :username, :password
+
+ def initialize(api_url, ca_pem, username, password)
+ @api_url = api_url
+ @ca_pem = ca_pem
+ @username = username
+ @password = password
+ end
+
+ def execute
+ read_secrets.each do |secret|
+ name = secret.dig('metadata', 'name')
+ if /default-token/ =~ name
+ token_base64 = secret.dig('data', 'token')
+ return Base64.decode64(token_base64) if token_base64
+ end
+ end
+
+ nil
+ end
+
+ private
+
+ def read_secrets
+ kubeclient = build_kubeclient!
+
+ kubeclient.get_secrets.as_json
+ rescue KubeException => err
+ raise err unless err.error_code == 404
+ []
+ end
+
+ def build_kubeclient!(api_path: 'api', api_version: 'v1')
+ raise "Incomplete settings" unless api_url && username && password
+
+ ::Kubeclient::Client.new(
+ join_api_url(api_path),
+ api_version,
+ auth_options: { username: username, password: password },
+ ssl_options: kubeclient_ssl_options,
+ http_proxy_uri: ENV['http_proxy']
+ )
+ end
+
+ def join_api_url(api_path)
+ url = URI.parse(api_url)
+ prefix = url.path.sub(%r{/+\z}, '')
+
+ url.path = [prefix, api_path].join("/")
+
+ url.to_s
+ end
+
+ def kubeclient_ssl_options
+ opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
+
+ if ca_pem.present?
+ opts[:cert_store] = OpenSSL::X509::Store.new
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+ end
+
+ opts
+ end
+ end
+end
diff --git a/app/services/ci/finalize_cluster_creation_service.rb b/app/services/ci/finalize_cluster_creation_service.rb
new file mode 100644
index 00000000000..347875c5697
--- /dev/null
+++ b/app/services/ci/finalize_cluster_creation_service.rb
@@ -0,0 +1,33 @@
+module Ci
+ class FinalizeClusterCreationService
+ def execute(cluster)
+ api_client =
+ GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
+
+ begin
+ gke_cluster = api_client.projects_zones_clusters_get(
+ cluster.gcp_project_id,
+ cluster.gcp_cluster_zone,
+ cluster.gcp_cluster_name)
+ rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
+ return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ end
+
+ endpoint = gke_cluster.endpoint
+ api_url = 'https://' + endpoint
+ ca_cert = Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate)
+ username = gke_cluster.master_auth.username
+ password = gke_cluster.master_auth.password
+
+ kubernetes_token = Ci::FetchKubernetesTokenService.new(
+ api_url, ca_cert, username, password).execute
+
+ unless kubernetes_token
+ return cluster.make_errored!('Failed to get a default token of kubernetes')
+ end
+
+ Ci::IntegrateClusterService.new.execute(
+ cluster, endpoint, ca_cert, kubernetes_token, username, password)
+ end
+ end
+end
diff --git a/app/services/ci/integrate_cluster_service.rb b/app/services/ci/integrate_cluster_service.rb
new file mode 100644
index 00000000000..d123ce8d26b
--- /dev/null
+++ b/app/services/ci/integrate_cluster_service.rb
@@ -0,0 +1,26 @@
+module Ci
+ class IntegrateClusterService
+ def execute(cluster, endpoint, ca_cert, token, username, password)
+ Gcp::Cluster.transaction do
+ cluster.update!(
+ enabled: true,
+ endpoint: endpoint,
+ ca_cert: ca_cert,
+ kubernetes_token: token,
+ username: username,
+ password: password,
+ service: cluster.project.find_or_initialize_service('kubernetes'),
+ status_event: :make_created)
+
+ cluster.service.update!(
+ active: true,
+ api_url: cluster.api_url,
+ ca_pem: ca_cert,
+ namespace: cluster.project_namespace,
+ token: token)
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ cluster.make_errored!("Failed to integrate cluster into kubernetes_service: #{e.message}")
+ end
+ end
+end
diff --git a/app/services/ci/provision_cluster_service.rb b/app/services/ci/provision_cluster_service.rb
new file mode 100644
index 00000000000..52d80b01813
--- /dev/null
+++ b/app/services/ci/provision_cluster_service.rb
@@ -0,0 +1,36 @@
+module Ci
+ class ProvisionClusterService
+ def execute(cluster)
+ api_client =
+ GoogleApi::CloudPlatform::Client.new(cluster.gcp_token, nil)
+
+ begin
+ operation = api_client.projects_zones_clusters_create(
+ cluster.gcp_project_id,
+ cluster.gcp_cluster_zone,
+ cluster.gcp_cluster_name,
+ cluster.gcp_cluster_size,
+ machine_type: cluster.gcp_machine_type)
+ rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
+ return cluster.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ end
+
+ unless operation.status == 'RUNNING' || operation.status == 'PENDING'
+ return cluster.make_errored!("Operation status is unexpected; #{operation.status_message}")
+ end
+
+ cluster.gcp_operation_id = api_client.parse_operation_id(operation.self_link)
+
+ unless cluster.gcp_operation_id
+ return cluster.make_errored!('Can not find operation_id from self_link')
+ end
+
+ if cluster.make_creating
+ WaitForClusterCreationWorker.perform_in(
+ WaitForClusterCreationWorker::INITIAL_INTERVAL, cluster.id)
+ else
+ return cluster.make_errored!("Failed to update cluster record; #{cluster.errors}")
+ end
+ end
+ end
+end
diff --git a/app/services/ci/update_cluster_service.rb b/app/services/ci/update_cluster_service.rb
new file mode 100644
index 00000000000..70d88fca660
--- /dev/null
+++ b/app/services/ci/update_cluster_service.rb
@@ -0,0 +1,22 @@
+module Ci
+ class UpdateClusterService < BaseService
+ def execute(cluster)
+ Gcp::Cluster.transaction do
+ cluster.update!(params)
+
+ if params['enabled'] == 'true'
+ cluster.service.update!(
+ active: true,
+ api_url: cluster.api_url,
+ ca_pem: cluster.ca_cert,
+ namespace: cluster.project_namespace,
+ token: cluster.kubernetes_token)
+ else
+ cluster.service.update!(active: false)
+ end
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ cluster.errors.add(:base, e.message)
+ end
+ end
+end
diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb
index ace49889097..5bbceeb3b3f 100644
--- a/app/services/emails/base_service.rb
+++ b/app/services/emails/base_service.rb
@@ -1,8 +1,8 @@
module Emails
class BaseService
- def initialize(user, opts)
- @user = user
- @email = opts[:email]
+ def initialize(current_user, params = {})
+ @current_user, @params = current_user, params.dup
+ @user = params.delete(:user)
end
end
end
diff --git a/app/services/emails/confirm_service.rb b/app/services/emails/confirm_service.rb
new file mode 100644
index 00000000000..b5301bf2b82
--- /dev/null
+++ b/app/services/emails/confirm_service.rb
@@ -0,0 +1,7 @@
+module Emails
+ class ConfirmService < ::Emails::BaseService
+ def execute(email)
+ email.resend_confirmation_instructions
+ end
+ end
+end
diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb
index b6491ee9804..94a841af7c3 100644
--- a/app/services/emails/create_service.rb
+++ b/app/services/emails/create_service.rb
@@ -1,7 +1,7 @@
module Emails
class CreateService < ::Emails::BaseService
- def execute
- @user.emails.create(email: @email)
+ def execute(extra_params = {})
+ @user.emails.create(@params.merge(extra_params))
end
end
end
diff --git a/app/services/emails/destroy_service.rb b/app/services/emails/destroy_service.rb
index d586b9dfe0c..1ed131fe326 100644
--- a/app/services/emails/destroy_service.rb
+++ b/app/services/emails/destroy_service.rb
@@ -1,13 +1,13 @@
module Emails
class DestroyService < ::Emails::BaseService
- def execute
- Email.find_by_email!(@email).destroy && update_secondary_emails!
+ def execute(email)
+ email.destroy && update_secondary_emails!
end
private
def update_secondary_emails!
- result = ::Users::UpdateService.new(@user).execute do |user|
+ result = ::Users::UpdateService.new(@current_user, user: @user).execute do |user|
user.update_secondary_emails!
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 12604e7eb5d..f83ece7098f 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -43,6 +43,10 @@ class IssuableBaseService < BaseService
SystemNoteService.change_time_spent(issuable, issuable.project, current_user)
end
+ def create_discussion_lock_note(issuable)
+ SystemNoteService.discussion_lock(issuable, current_user)
+ end
+
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
@@ -57,6 +61,7 @@ class IssuableBaseService < BaseService
params.delete(:due_date)
params.delete(:canonical_issue_id)
params.delete(:project)
+ params.delete(:discussion_locked)
end
filter_assignee(issuable)
@@ -236,6 +241,7 @@ class IssuableBaseService < BaseService
handle_common_system_notes(issuable, old_labels: old_labels)
end
+ change_discussion_lock(issuable)
handle_changes(
issuable,
old_labels: old_labels,
@@ -294,6 +300,12 @@ class IssuableBaseService < BaseService
end
end
+ def change_discussion_lock(issuable)
+ if issuable.previous_changes.include?('discussion_locked')
+ create_discussion_lock_note(issuable)
+ end
+ end
+
def toggle_award(issuable)
award = params.delete(:emoji_award)
if award
diff --git a/app/services/keys/last_used_service.rb b/app/services/keys/last_used_service.rb
index 066f3246158..dbd79f7da55 100644
--- a/app/services/keys/last_used_service.rb
+++ b/app/services/keys/last_used_service.rb
@@ -16,6 +16,8 @@ module Keys
end
def update?
+ return false if ::Gitlab::Database.read_only?
+
last_used = key.last_used_at
return false if last_used && (Time.zone.now - last_used) <= TIMEOUT
diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb
new file mode 100644
index 00000000000..ba6853b835a
--- /dev/null
+++ b/app/services/merge_requests/ff_merge_service.rb
@@ -0,0 +1,24 @@
+module MergeRequests
+ # MergeService class
+ #
+ # Do git fast-forward merge and in case of success
+ # mark merge request as merged and execute all hooks and notifications
+ # Executed when you do fast-forward merge via GitLab UI
+ #
+ class FfMergeService < MergeRequests::MergeService
+ private
+
+ def commit
+ repository.ff_merge(current_user,
+ source,
+ merge_request.target_branch,
+ merge_request: merge_request)
+ rescue Gitlab::Git::HooksService::PreReceiveError => e
+ raise MergeError, e.message
+ rescue StandardError => e
+ raise MergeError, "Something went wrong during merge: #{e.message}"
+ ensure
+ merge_request.update(in_progress_merge_commit_sha: nil)
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 07cbd8f92a9..a110abf8256 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -11,16 +11,21 @@ module MergeRequests
attr_reader :merge_request, :source
def execute(merge_request)
+ if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService)
+ FfMergeService.new(project, current_user, params).execute(merge_request)
+ return
+ end
+
@merge_request = merge_request
unless @merge_request.mergeable?
- return log_merge_error('Merge request is not mergeable', save_message_on_model: true)
+ return handle_merge_error(log_message: 'Merge request is not mergeable', save_message_on_model: true)
end
@source = find_merge_source
unless @source
- return log_merge_error('No source for merge', save_message_on_model: true)
+ return handle_merge_error(log_message: 'No source for merge', save_message_on_model: true)
end
merge_request.in_locked_state do
@@ -31,8 +36,7 @@ module MergeRequests
end
end
rescue MergeError => e
- clean_merge_jid
- log_merge_error(e.message, save_message_on_model: true)
+ handle_merge_error(log_message: e.message, save_message_on_model: true)
end
private
@@ -74,10 +78,16 @@ module MergeRequests
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end
- def log_merge_error(message, save_message_on_model: false)
- Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
+ # Logs merge error message and cleans `MergeRequest#merge_jid`.
+ #
+ def handle_merge_error(log_message:, save_message_on_model: false)
+ Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
- @merge_request.update(merge_error: message) if save_message_on_model
+ if save_message_on_model
+ @merge_request.update(merge_error: log_message, merge_jid: nil)
+ else
+ clean_merge_jid
+ end
end
def merge_request_info
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index 261a8bfa200..b1d6bac4d4a 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -14,6 +14,7 @@ module MergeRequests
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
invalidate_cache_counts(merge_request, users: merge_request.assignees)
+ merge_request.update_project_counter_caches
end
private
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e2a80db06a6..8d5da459882 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -31,13 +31,6 @@ class NotificationService
end
end
- # Always notify user about email added to profile
- def new_email(email)
- if email.user&.can?(:receive_notifications)
- mailer.new_email_email(email.id).deliver_later
- end
- end
-
# When create an issue we should send an email to:
#
# * issue assignee if their notification level is not Disabled
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 54eb75ab9bf..19d75ff2efa 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -22,6 +22,13 @@ module Projects
Projects::UnlinkForkService.new(project, current_user).execute
+ # The project is not necessarily a fork, so update the fork network originating
+ # from this project
+ if fork_network = project.root_of_fork_network
+ fork_network.update(root_project: nil,
+ deleted_root_project_name: project.full_name)
+ end
+
attempt_destroy_transaction(project)
system_hook_service.execute_hooks_for(project, :destroy)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index ad67e68a86a..eb5cce5ab98 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -23,11 +23,31 @@ module Projects
refresh_forks_count
+ link_fork_network(new_project)
+
new_project
end
private
+ def fork_network
+ if @project.fork_network
+ @project.fork_network
+ elsif forked_from_project = @project.forked_from_project
+ # TODO: remove this case when all background migrations have completed
+ # this only happens when a project had a `forked_project_link` that was
+ # not migrated to the `fork_network` relation
+ forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
+ else
+ @project.create_root_of_fork_network
+ end
+ end
+
+ def link_fork_network(new_project)
+ fork_network.fork_network_members.create(project: new_project,
+ forked_from_project: @project)
+ end
+
def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache
end
diff --git a/app/services/projects/hashed_storage_migration_service.rb b/app/services/projects/hashed_storage_migration_service.rb
new file mode 100644
index 00000000000..41259de3a16
--- /dev/null
+++ b/app/services/projects/hashed_storage_migration_service.rb
@@ -0,0 +1,68 @@
+module Projects
+ class HashedStorageMigrationService < BaseService
+ include Gitlab::ShellAdapter
+
+ attr_reader :old_disk_path, :new_disk_path
+
+ def initialize(project, logger = nil)
+ @project = project
+ @logger ||= Rails.logger
+ end
+
+ def execute
+ return if project.hashed_storage?
+
+ @old_disk_path = project.disk_path
+ has_wiki = project.wiki.repository_exists?
+
+ project.storage_version = Storage::HashedProject::STORAGE_VERSION
+ project.ensure_storage_path_exists
+
+ @new_disk_path = project.disk_path
+
+ result = move_repository(@old_disk_path, @new_disk_path)
+
+ if has_wiki
+ result &&= move_repository("#{@old_disk_path}.wiki", "#{@new_disk_path}.wiki")
+ end
+
+ unless result
+ rollback_folder_move
+ return
+ end
+
+ project.repository_read_only = false
+ project.save!
+
+ block_given? ? yield : result
+ end
+
+ private
+
+ def move_repository(from_name, to_name)
+ from_exists = gitlab_shell.exists?(project.repository_storage_path, "#{from_name}.git")
+ to_exists = gitlab_shell.exists?(project.repository_storage_path, "#{to_name}.git")
+
+ # If we don't find the repository on either original or target we should log that as it could be an issue if the
+ # project was not originally empty.
+ if !from_exists && !to_exists
+ logger.warn "Can't find a repository on either source or target paths for #{project.full_path} (ID=#{project.id}) ..."
+ return false
+ elsif !from_exists
+ # Repository have been moved already.
+ return true
+ end
+
+ gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name)
+ end
+
+ def rollback_folder_move
+ move_repository(@new_disk_path, @old_disk_path)
+ move_repository("#{@new_disk_path}.wiki", "#{@old_disk_path}.wiki")
+ end
+
+ def logger
+ @logger
+ end
+ end
+end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index f30b40423c8..abe414d0c05 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -16,6 +16,7 @@ module Projects
refresh_forks_count(@project.forked_from_project)
@project.forked_project_link.destroy
+ @project.fork_network_member.destroy
end
def refresh_forks_count(project)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1f66a2668f9..7b32e215c7f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -591,6 +591,13 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
end
+ def discussion_lock(issuable, author)
+ action = issuable.discussion_locked? ? 'locked' : 'unlocked'
+ body = "#{action} this issue"
+
+ create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action))
+ end
+
private
def notes_for_mentioner(mentioner, noteable, notes)
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index b3f4a72d6fe..cc76d0df3a1 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -11,7 +11,7 @@ module Tags
begin
new_tag = repository.add_tag(current_user, tag_name, target, message)
- rescue Rugged::TagError
+ rescue Gitlab::Git::Repository::TagExistsError
return error("Tag #{tag_name} already exists")
rescue Gitlab::Git::HooksService::PreReceiveError => ex
return error(ex.message)
diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb
index ab532a1fdcf..5803404c3c8 100644
--- a/app/services/users/activity_service.rb
+++ b/app/services/users/activity_service.rb
@@ -14,7 +14,7 @@ module Users
private
def record_activity
- Gitlab::UserActivities.record(@author.id)
+ Gitlab::UserActivities.record(@author.id) if Gitlab::Database.read_write?
Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username})")
end
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index 6188b8a4349..15ca1a55a5b 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -2,22 +2,21 @@ module Users
class UpdateService < BaseService
include NewUserNotifier
- def initialize(user, params = {})
- @user = user
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @user = params.delete(:user)
@params = params.dup
end
def execute(validate: true, &block)
yield(@user) if block_given?
- assign_attributes(&block)
-
user_exists = @user.persisted?
- if @user.save(validate: validate)
- notify_new_user(@user, nil) unless user_exists
+ assign_attributes(&block)
- success
+ if @user.save(validate: validate)
+ notify_success(user_exists)
else
error(@user.errors.full_messages.uniq.join('. '))
end
@@ -33,6 +32,12 @@ module Users
private
+ def notify_success(user_exists)
+ notify_new_user(@user, nil) unless user_exists
+
+ success
+ end
+
def assign_attributes(&block)
if @user.user_synced_attributes_metadata
params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 0310498ae54..7066ed12b95 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -3,7 +3,7 @@
%div{ class: container_class }
- .top-area
+ .top-area.scrolling-tabs-container.inner-page-scroll-tabs
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 3f202fbf4fe..4d8754afdd2 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -4,7 +4,7 @@
%div{ class: container_class }
- .top-area
+ .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.prepend-top-default
.search-holder
= render 'shared/projects/search_form', autofocus: true, icon: true
diff --git a/app/views/devise/mailer/_confirmation_instructions_account.html.haml b/app/views/devise/mailer/_confirmation_instructions_account.html.haml
new file mode 100644
index 00000000000..65565b7b8a8
--- /dev/null
+++ b/app/views/devise/mailer/_confirmation_instructions_account.html.haml
@@ -0,0 +1,16 @@
+- confirmation_link = confirmation_url(@resource, confirmation_token: @token)
+- if @resource.unconfirmed_email.present?
+ #content
+ = email_default_heading(@resource.unconfirmed_email)
+ %p Click the link below to confirm your email address.
+ #cta
+ = link_to 'Confirm your email address', confirmation_link
+- else
+ #content
+ - if Gitlab.com?
+ = email_default_heading('Thanks for signing up to GitLab!')
+ - else
+ = email_default_heading("Welcome, #{@resource.name}!")
+ %p To get started, click the link below to confirm your account.
+ #cta
+ = link_to 'Confirm your account', confirmation_link
diff --git a/app/views/devise/mailer/_confirmation_instructions_account.text.erb b/app/views/devise/mailer/_confirmation_instructions_account.text.erb
new file mode 100644
index 00000000000..01f09aa763d
--- /dev/null
+++ b/app/views/devise/mailer/_confirmation_instructions_account.text.erb
@@ -0,0 +1,14 @@
+<% if @resource.unconfirmed_email.present? %>
+<%= @resource.unconfirmed_email %>,
+
+Use the link below to confirm your email address.
+<% else %>
+ <% if Gitlab.com? %>
+Thanks for signing up to GitLab!
+ <% else %>
+Welcome, <%= @resource.name %>!
+ <% end %>
+To get started, use the link below to confirm your account.
+<% end %>
+
+<%= confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml b/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml
new file mode 100644
index 00000000000..3d0a1f622a5
--- /dev/null
+++ b/app/views/devise/mailer/_confirmation_instructions_secondary.html.haml
@@ -0,0 +1,8 @@
+#content
+ = email_default_heading("#{@resource.user.name}, you've added an additional email!")
+ %p Click the link below to confirm your email address (#{@resource.email})
+ #cta
+ = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
+ %p
+ If this email was added in error, you can remove it here:
+ = link_to "Emails", profile_emails_url
diff --git a/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb
new file mode 100644
index 00000000000..a3b28cb0b84
--- /dev/null
+++ b/app/views/devise/mailer/_confirmation_instructions_secondary.text.erb
@@ -0,0 +1,7 @@
+<%= @resource.user.name %>, you've added an additional email!
+
+Use the link below to confirm your email address (<%= @resource.email %>)
+
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+If this email was added in error, you can remove it here: <%= profile_emails_url %>
diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml
index a508b7537a2..50ee7b53d8f 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.haml
+++ b/app/views/devise/mailer/confirmation_instructions.html.haml
@@ -1,15 +1 @@
-- if @resource.unconfirmed_email.present?
- #content
- = email_default_heading(@resource.unconfirmed_email)
- %p Click the link below to confirm your email address.
- #cta
- = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
-- else
- #content
- - if Gitlab.com?
- = email_default_heading('Thanks for signing up to GitLab!')
- - else
- = email_default_heading("Welcome, #{@resource.name}!")
- %p To get started, click the link below to confirm your account.
- #cta
- = link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
+= render partial: "confirmation_instructions_#{@resource.is_a?(User) ? 'account' : 'secondary'}"
diff --git a/app/views/devise/mailer/confirmation_instructions.text.erb b/app/views/devise/mailer/confirmation_instructions.text.erb
index 9f76edb76a4..05fddddf415 100644
--- a/app/views/devise/mailer/confirmation_instructions.text.erb
+++ b/app/views/devise/mailer/confirmation_instructions.text.erb
@@ -1,9 +1 @@
-Welcome, <%= @resource.name %>!
-
-<% if @resource.unconfirmed_email.present? %>
-You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:
-<% else %>
-You can confirm your account through the link below:
-<% end %>
-
-<%= confirmation_url(@resource, confirmation_token: @token) %>
+<%= render partial: "confirmation_instructions_#{@resource.is_a?(User) ? 'account' : 'secondary'}" %> \ No newline at end of file
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
index e6d307e5568..52279d0a870 100644
--- a/app/views/discussions/_diff_discussion.html.haml
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -1,6 +1,10 @@
-- expanded = local_assigns.fetch(:expanded, true)
-%tr.notes_holder{ class: ('hide' unless expanded) }
- %td.notes_line{ colspan: 2 }
- %td.notes_content
- .content{ class: ('hide' unless expanded) }
- = render partial: "discussions/notes", collection: discussions, as: :discussion
+- if local_assigns[:on_image]
+ = render partial: "discussions/notes", collection: discussions, as: :discussion
+- else
+ -# Text diff discussions
+ - expanded = local_assigns.fetch(:expanded, true)
+ %tr.notes_holder{ class: ('hide' unless expanded) }
+ %td.notes_line{ colspan: 2 }
+ %td.notes_content
+ .content{ class: ('hide' unless expanded) }
+ = render partial: "discussions/notes", collection: discussions, as: :discussion
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 4a41be972da..636d06cab53 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -1,18 +1,27 @@
- diff_file = discussion.diff_file
- blob = discussion.blob
+- discussions = { discussion.original_line_code => [discussion] }
+- diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file'
-.diff-file.file-holder
+.diff-file.file-holder{ class: diff_file_class }
.js-file-title.file-title.file-title-flex-parent
.file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
- .diff-content.code.js-syntax-highlight
- %table
- - discussions = { discussion.original_line_code => [discussion] }
- = render partial: "projects/diffs/line",
- collection: discussion.truncated_diff_lines,
- as: :line,
- locals: { diff_file: diff_file,
- discussions: discussions,
- discussion_expanded: true,
- plain: true }
+ - if diff_file.text?
+ .diff-content.code.js-syntax-highlight
+ %table
+ = render partial: "projects/diffs/line",
+ collection: discussion.truncated_diff_lines,
+ as: :line,
+ locals: { diff_file: diff_file,
+ discussions: discussions,
+ discussion_expanded: true,
+ plain: true }
+ - else
+ - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff'
+
+ = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
+
+ .note-container
+ = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true }
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index db5ab939948..9efcfef690f 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -1,6 +1,19 @@
-.discussion-notes
- %ul.notes{ data: { discussion_id: discussion.id } }
- = render partial: "shared/notes/note", collection: discussion.notes, as: :note
+- disable_collapse = local_assigns.fetch(:disable_collapse, false)
+- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse
+- badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter]
+- show_toggle = local_assigns.fetch(:show_toggle, true)
+- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
+
+.discussion-notes{ class: collapsed_class }
+ -# Save the first note position data so that we have a reference and can go
+ -# to the first note position when we click on a badge diff discussion
+ %ul.notes{ id: "discussion_#{discussion.id}", data: { discussion_id: discussion.id, position: discussion.notes[0].position.to_json } }
+ - if discussion.try(:on_image?) && show_toggle
+ %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' }
+ = sprite_icon('collapse', css_class: 'collapse-icon')
+ %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' }
+ = badge_counter
+ = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter, show_image_comment_badge: show_image_comment_badge }
.flash-container
diff --git a/app/views/groups/milestones/_header_title.html.haml b/app/views/groups/milestones/_header_title.html.haml
index d7fabf53587..24eb39b8e2f 100644
--- a/app/views/groups/milestones/_header_title.html.haml
+++ b/app/views/groups/milestones/_header_title.html.haml
@@ -1 +1,2 @@
-- header_title group_title(@group, "Milestones", group_milestones_path(@group))
+- breadcrumb_title @milestone.title
+- add_to_breadcrumbs "Milestones", group_milestones_path(@group)
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index e3a9e99250e..f1b32274664 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -37,9 +37,9 @@
- if content_for?(:library_javascripts)
= yield :library_javascripts
+ = javascript_include_tag asset_path("locale/#{I18n.locale.to_s || I18n.default_locale.to_s}/app.js") unless I18n.locale == :en
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
- = webpack_bundle_tag "locale"
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index cd7a47da4a1..29387d6627e 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -21,8 +21,8 @@
%a
Loading...
= dropdown_loading
- %i.search-icon
- %i.clear-icon.js-clear-input
+ = sprite_icon('search', size: 16, css_class: 'search-icon')
+ = sprite_icon('close', size: 16, css_class: 'clear-icon js-clear-input')
= hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index d8fc371497d..5ff6ac5fc00 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,4 +1,4 @@
-%header.navbar.navbar-gitlab.navbar-gitlab-new
+%header.navbar.navbar-gitlab
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
@@ -22,29 +22,29 @@
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('search')
+ = sprite_icon('search', size: 16)
- if current_user
- %li.user-counter
+ = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = custom_icon('issues')
+ = sprite_icon('issues', size: 16)
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
- %li.user-counter
+ = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = custom_icon('mr_bold')
+ = sprite_icon('git-merge', size: 16)
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count)
- %li.user-counter
+ = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = custom_icon('todo_done')
+ = sprite_icon('todo-done', size: 16)
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
- = custom_icon('caret_down')
+ = sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
@@ -73,7 +73,7 @@
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
- = icon('ellipsis-v', class: 'js-navbar-toggle-right')
- = icon('times', class: 'js-navbar-toggle-left')
+ = sprite_icon('more', size: 12, css_class: 'more-icon js-navbar-toggle-right')
+ = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
= render 'shared/outdated_browser'
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 63d1c077ecd..088f2785092 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,7 +1,7 @@
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
- = custom_icon('plus_square')
- = custom_icon('caret_down')
+ = sprite_icon('plus-square', size: 16)
+ = sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
- if @group&.persisted?
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index 7bd3f5306a2..002922e13f1 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -16,5 +16,5 @@
= breadcrumb_list_item link_to(extra[:text], extra[:link])
= render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after
%li
- %h2.breadcrumbs-sub-title= @breadcrumb_title
+ %h2.breadcrumbs-sub-title= link_to @breadcrumb_title, breadcrumb_title_link
= yield :header_content
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index c254ee02dd8..e0d8d9cb402 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -2,7 +2,7 @@
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects" }) do
%a{ href: "#", data: { toggle: "dropdown" } }
Projects
- = custom_icon('caret_down')
+ = sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.projects-dropdown-menu
= render "layouts/nav/projects_dropdown/show"
@@ -25,7 +25,7 @@
%li.header-more.dropdown.hidden-lg
%a{ href: "#", data: { toggle: "dropdown" } }
More
- = custom_icon('caret_down')
+ = sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu
%ul
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "visible-xs" }) do
@@ -54,7 +54,7 @@
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
= link_to admin_root_path, class: 'admin-icon', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('wrench fw')
+ = sprite_icon('admin', size: 18)
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, class: 'admin-icon', title: 'Sherlock Transactions',
diff --git a/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml b/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml
index 28022eebb19..610ff9001f7 100644
--- a/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml
+++ b/app/views/layouts/nav/breadcrumbs/_collapsed_dropdown.html.haml
@@ -4,7 +4,7 @@
%li.dropdown
%button.text-expander.has-tooltip.js-breadcrumbs-collapsed-expander{ type: "button", data: { toggle: "dropdown", container: "body" }, "aria-label": button_tooltip, title: button_tooltip }
= icon("ellipsis-h")
- = icon("angle-right", class: "breadcrumbs-list-angle")
+ = sprite_icon("angle-right", css_class: "breadcrumbs-list-angle")
.dropdown-menu
%ul
- @breadcrumb_dropdown_links[dropdown_location].each_with_index do |link, index|
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 9eef006b6a8..0ec07605631 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -3,7 +3,7 @@
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
.avatar-container.s40.settings-avatar
- = icon('wrench')
+ = sprite_icon('admin', size: 24)
.sidebar-context-title Admin Area
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do
@@ -12,7 +12,6 @@
= sprite_icon('overview')
%span.nav-item-name
Overview
-
%ul.sidebar-sub-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: { class: "fly-out-top-item" } ) do
= link_to admin_root_path do
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index a015c94c60e..458b5010d36 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -3,7 +3,7 @@
.context-header
= link_to profile_path, title: 'Profile Settings' do
.avatar-container.s40.settings-avatar
- = icon('user')
+ = sprite_icon('user', size: 24)
.sidebar-context-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 8765b814405..759d6ff68ea 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -146,7 +146,7 @@
= number_with_delimiter(@project.open_merge_requests_count)
- if project_nav_tab? :pipelines
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters]) do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
.nav-icon-container
= sprite_icon('pipeline')
@@ -189,6 +189,12 @@
%span
Charts
+ - if project_nav_tab? :clusters
+ = nav_link(controller: :clusters) do
+ = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do
+ %span
+ Cluster
+
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
diff --git a/app/views/notify/new_email_email.html.haml b/app/views/notify/new_email_email.html.haml
deleted file mode 100644
index 4a0448a573c..00000000000
--- a/app/views/notify/new_email_email.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%p
- Hi #{@user.name}!
-%p
- A new email was added to your account:
-%p
- email:
- %code= @email.email
-%p
- If this email was added in error, you can remove it here:
- = link_to "Emails", profile_emails_url
diff --git a/app/views/notify/new_email_email.text.erb b/app/views/notify/new_email_email.text.erb
deleted file mode 100644
index 51cba99ad0d..00000000000
--- a/app/views/notify/new_email_email.text.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-Hi <%= @user.name %>!
-
-A new email was added to your account:
-
-email.................. <%= @email.email %>
-
-If this email was added in error, you can remove it here: <%= profile_emails_url %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 8abbd828032..7f79168dfb3 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -97,21 +97,29 @@
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.danger-title
- Remove account
+ = s_('Profiles|Delete account')
.col-lg-8
- if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p
- Deleting an account has the following effects:
+ = s_('Profiles|Deleting an account has the following effects:')
= render 'users/deletion_guidance', user: current_user
- = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+
+ #delete-account-modal{ data: { action_url: user_registration_path,
+ confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
+ username: current_user.username } }
+ %button.btn.btn-danger.disabled
+ = s_('Profiles|Delete account')
- else
- if @user.solo_owned_groups.present?
%p
- Your account is currently an owner in these groups:
+ = s_('Profiles|Your account is currently an owner in these groups:')
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
- You must transfer ownership or delete these groups before you can delete your account.
+ = s_('Profiles|You must transfer ownership or delete these groups before you can delete your account.')
- else
%p
- You don't have access to delete this user.
+ = s_("Profiles|You don't have access to delete this user.")
.append-bottom-default
+
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag('account')
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 612ecbbb96a..df1df4f5d72 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -32,19 +32,25 @@
All email addresses will be used to identify your commits.
%ul.well-list
%li
- = @primary
+ = render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.pull-right
%span.label.label-success Primary email
- - if @primary === current_user.public_email
+ - if @primary_email === current_user.public_email
%span.label.label-info Public email
- - if @primary === current_user.notification_email
+ - if @primary_email === current_user.notification_email
%span.label.label-info Notification email
- @emails.each do |email|
%li
- = email.email
+ = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.pull-right
- if email.email === current_user.public_email
%span.label.label-info Public email
- if email.email === current_user.notification_email
%span.label.label-info Notification email
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
+ - unless email.confirmed?
+ - confirm_title = "#{email.confirmation_sent_at ? 'Resend' : 'Send'} confirmation email"
+ = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning prepend-left-10'
+
+ = link_to profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-danger prepend-left-10' do
+ %span.sr-only Remove
+ = icon('trash')
diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml
index b04981f90e3..5ed517c1ef6 100644
--- a/app/views/profiles/gpg_keys/_key.html.haml
+++ b/app/views/profiles/gpg_keys/_key.html.haml
@@ -3,10 +3,17 @@
= icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info
- key.emails_with_verified_status.map do |email, verified|
- = render partial: 'email_with_badge', locals: { email: email, verified: verified }
+ = render partial: 'shared/email_with_badge', locals: { email: email, verified: verified }
.description
%code= key.fingerprint
+ - if key.subkeys.present?
+ .subkeys
+ %span.bold Subkeys:
+ %ul.subkeys-list
+ - key.subkeys.each do |subkey|
+ %li
+ %code= subkey.fingerprint
.pull-right
%span.key-created-at
created #{time_ago_with_tooltip(key.created_at)}
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 873b3045ea9..619b632918e 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,4 +1,6 @@
- empty_repo = @project.empty_repo?
+- fork_network = @project.fork_network
+- forked_from_project = @project.forked_from_project || fork_network&.root_project
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
@@ -12,11 +14,15 @@
- if @project.description.present?
= markdown_field(@project, :description)
- - if forked_from_project = @project.forked_from_project
+ - if @project.forked?
%p
- #{ s_('ForkedFromProjectPath|Forked from') }
- = link_to project_path(forked_from_project) do
- = forked_from_project.namespace.try(:name)
+ - if forked_from_project
+ #{ s_('ForkedFromProjectPath|Forked from') }
+ = link_to project_path(forked_from_project) do
+ = forked_from_project.full_name
+ - else
+ - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
+ = deleted_message % { project_name: fork_network.deleted_root_project_name }
.project-repo-buttons
.count-buttons
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 71424593f2e..770608eddff 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,5 +1,12 @@
- referenced_users = local_assigns.fetch(:referenced_users, nil)
+- if defined?(@merge_request) && @merge_request.discussion_locked?
+ .issuable-note-warning
+ = icon('lock', class: 'icon')
+ %span
+ = _('This merge request is locked.')
+ = _('Only project members can comment.')
+
.md-area
.md-header
%ul.nav-links.clearfix
diff --git a/app/views/projects/_merge_request_fast_forward_settings.html.haml b/app/views/projects/_merge_request_fast_forward_settings.html.haml
new file mode 100644
index 00000000000..9d357293a2f
--- /dev/null
+++ b/app/views/projects/_merge_request_fast_forward_settings.html.haml
@@ -0,0 +1,13 @@
+- form = local_assigns.fetch(:form)
+- project = local_assigns.fetch(:project)
+
+.radio
+ = label_tag :project_merge_method_ff do
+ = form.radio_button :merge_method, :ff, class: "js-merge-method-radio"
+ %strong Fast-forward merge
+ %br
+ %span.descr
+ No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
+ %br
+ %span.descr
+ When fast-forward merge is not possible, the user must first rebase locally.
diff --git a/app/views/projects/_merge_request_rebase_settings.html.haml b/app/views/projects/_merge_request_rebase_settings.html.haml
new file mode 100644
index 00000000000..c52e09573a6
--- /dev/null
+++ b/app/views/projects/_merge_request_rebase_settings.html.haml
@@ -0,0 +1,13 @@
+- form = local_assigns.fetch(:form)
+
+.radio
+ = label_tag :project_merge_method_rebase_merge do
+ = form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio"
+ %strong Merge commit with semi-linear history
+ %br
+ %span.descr
+ A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible.
+ This way you could make sure that if this merge request would build, after merging to target branch it would also build.
+ %br
+ %span.descr
+ When fast-forward merge is not possible, the user must first rebase locally.
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index cc5afa943cf..fd0c419cdac 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -1,3 +1,18 @@
- form = local_assigns.fetch(:form)
+.form-group
+ = label_tag :merge_method_merge, class: 'label-light' do
+ Merge method
+ .radio
+ = label_tag :project_merge_method_merge do
+ = form.radio_button :merge_method, :merge, class: "js-merge-method-radio"
+ %strong Merge commit
+ %br
+ %span.descr
+ A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
+
+ = render 'merge_request_rebase_settings', form: form
+
+ = render 'merge_request_fast_forward_settings', project: @project, form: form
+
= render 'projects/merge_request_merge_settings', form: form
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index 8edb9be049a..a97ddb3c377 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -1,10 +1,17 @@
+- blob = file.blob
- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
+- external_link = blob.external_link?(@build)
-%tr.tree-item{ 'data-link' => path_to_file }
- - blob = file.blob
+%tr.tree-item.js-artifact-tree-row{ data: { link: path_to_file, external_link: "#{external_link}" } }
%td.tree-item-file-name
= tree_icon('file', blob.mode, blob.name)
- = link_to path_to_file do
- %span.str-truncated= blob.name
+ - if external_link
+ = link_to path_to_file, class: 'tree-item-file-external-link js-artifact-tree-tooltip',
+ target: '_blank', rel: 'noopener noreferrer', title: _('Opens in a new window') do
+ %span.str-truncated>= blob.name
+ = icon('external-link', class: 'js-artifact-tree-external-icon')
+ - else
+ = link_to path_to_file do
+ %span.str-truncated= blob.name
%td
= number_to_human_size(blob.size, precision: 2)
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 05857573297..fe02cbcbf95 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -3,7 +3,7 @@
= render "projects/jobs/header", show_controls: false
-- add_to_breadcrumbs(_('Jobs'), project_jobs_path(@project))
+- add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project))
- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
.tree-holder
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index d1d448f0d4c..ea7a71792a3 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -5,25 +5,24 @@
= diff_match_line @form.since, @form.since, text: @match_line, view: diff_view
- @lines.each_with_index do |line, index|
- - line_new = index + @form.since
- - line_old = line_new - @form.offset
- - line_content = capture do
- %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line}
- %tr.line_holder.diff-expanded{ id: line_old, class: line_class }
+ - line_number_new = index + @form.since
+ - line_number_old = line_number_new - @form.offset
+ - line[0, 0] = ' ' * @form.indent
+ %tr.line_holder.diff-expanded{ id: line_number_old, class: line_class }
- case diff_view
- when :inline
- %td.old_line.diff-line-num{ data: { linenumber: line_old } }
- %a{ href: "#", data: { linenumber: line_old }, disabled: true }
- %td.new_line.diff-line-num{ data: { linenumber: line_new } }
- %a{ href: "#", data: { linenumber: line_new }, disabled: true }
- = line_content
+ %td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
+ %a{ href: "#", data: { linenumber: line_number_old }, disabled: true }
+ %td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
+ %a{ href: "#", data: { linenumber: line_number_new }, disabled: true }
+ %td.line_content.noteable_line{ class: line_class }= line
- when :parallel
- %td.old_line.diff-line-num{ data: { linenumber: line_old } }
- %a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true }
- = line_content
- %td.new_line.diff-line-num{ data: { linenumber: line_new } }
- %a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true }
- = line_content
+ %td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
+ %a{ href: "##{line_number_old}", data: { linenumber: line_number_old }, disabled: true }
+ %td.line_content.noteable_line.left-side{ class: line_class }= line
+ %td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
+ %a{ href: "##{line_number_new}", data: { linenumber: line_number_new }, disabled: true }
+ %td.line_content.noteable_line.right-side{ class: line_class }= line
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
%tr.line_holder{ id: @form.to, class: line_class }
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index f45cc7f0f45..f880556a9f7 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -4,12 +4,11 @@
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- - elsif !current_user.can_create_project?
- = link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
- = custom_icon('icon_fork')
- %span= s_('CreateNewFork|Fork')
- else
- = link_to new_project_fork_path(@project), class: 'btn' do
+ - can_create_fork = current_user.can?(:create_fork)
+ = link_to new_project_fork_path(@project),
+ class: "btn btn-default #{'has-tooltip disabled' unless can_create_fork}",
+ title: (_('You have reached your project limit') unless can_create_fork) do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
diff --git a/app/views/projects/clusters/_form.html.haml b/app/views/projects/clusters/_form.html.haml
new file mode 100644
index 00000000000..371cdb1e403
--- /dev/null
+++ b/app/views/projects/clusters/_form.html.haml
@@ -0,0 +1,37 @@
+.row
+ .col-sm-8.col-sm-offset-4
+ %p
+ - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page}
+
+ = form_for [@project.namespace.becomes(Namespace), @project, @cluster] do |field|
+ = form_errors(@cluster)
+ .form-group
+ = field.label :gcp_cluster_name, s_('ClusterIntegration|Cluster name')
+ = field.text_field :gcp_cluster_name, class: 'form-control'
+
+ .form-group
+ = field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID')
+ = link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer')
+ = field.text_field :gcp_project_id, class: 'form-control'
+
+ .form-group
+ = field.label :gcp_cluster_zone, s_('ClusterIntegration|Zone')
+ = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
+ = field.text_field :gcp_cluster_zone, class: 'form-control', placeholder: 'us-central1-a'
+
+ .form-group
+ = field.label :gcp_cluster_size, s_('ClusterIntegration|Number of nodes')
+ = field.text_field :gcp_cluster_size, class: 'form-control', placeholder: '3'
+
+ .form-group
+ = field.label :gcp_machine_type, s_('ClusterIntegration|Machine type')
+ = link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer')
+ = field.text_field :gcp_machine_type, class: 'form-control', placeholder: 'n1-standard-4'
+
+ .form-group
+ = field.label :project_namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
+ = field.text_field :project_namespace, class: 'form-control', placeholder: @cluster.project_namespace_placeholder
+
+ .form-group
+ = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save'
diff --git a/app/views/projects/clusters/_header.html.haml b/app/views/projects/clusters/_header.html.haml
new file mode 100644
index 00000000000..0134d46491c
--- /dev/null
+++ b/app/views/projects/clusters/_header.html.haml
@@ -0,0 +1,14 @@
+%h4.prepend-top-0
+ = s_('ClusterIntegration|Create new cluster on Google Container Engine')
+%p
+ = s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
+%ul
+ %li
+ - link_to_container_engine = link_to(s_('ClusterIntegration|access to Google Container Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Your account must have %{link_to_container_engine}').html_safe % { link_to_container_engine: link_to_container_engine }
+ %li
+ - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/container-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
+ %li
+ - link_to_container_project = link_to(s_('ClusterIntegration|Google Container Engine project'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|A %{link_to_container_project} must have been created under this account').html_safe % { link_to_container_project: link_to_container_project }
diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/projects/clusters/_sidebar.html.haml
new file mode 100644
index 00000000000..761879db32b
--- /dev/null
+++ b/app/views/projects/clusters/_sidebar.html.haml
@@ -0,0 +1,7 @@
+%h4.prepend-top-0
+ = s_('ClusterIntegration|Cluster integration')
+%p
+ = s_('ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
+%p
+ - link = link_to(s_('ClusterIntegration|cluster'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Learn more about %{link_to_documentation}').html_safe % { link_to_documentation: link }
diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/login.html.haml
new file mode 100644
index 00000000000..ae132672b7e
--- /dev/null
+++ b/app/views/projects/clusters/login.html.haml
@@ -0,0 +1,16 @@
+- breadcrumb_title "Cluster"
+- page_title _("Login")
+
+.row.prepend-top-default
+ .col-sm-4
+ = render 'sidebar'
+ .col-sm-8
+ = render 'header'
+.row
+ .col-sm-8.col-sm-offset-4.signin-with-google
+ - if @authorize_url
+ = link_to @authorize_url do
+ = image_tag('auth_buttons/signin_with_google.png')
+ - else
+ - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer')
+ = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml
new file mode 100644
index 00000000000..c538d41ffad
--- /dev/null
+++ b/app/views/projects/clusters/new.html.haml
@@ -0,0 +1,9 @@
+- breadcrumb_title "Cluster"
+- page_title _("New Cluster")
+
+.row.prepend-top-default
+ .col-sm-4
+ = render 'sidebar'
+ .col-sm-8
+ = render 'header'
+= render 'form'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
new file mode 100644
index 00000000000..aee6f904a62
--- /dev/null
+++ b/app/views/projects/clusters/show.html.haml
@@ -0,0 +1,70 @@
+- breadcrumb_title "Cluster"
+- page_title _("Cluster")
+
+- status_path = status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster) && @cluster.on_creation?
+.row.prepend-top-default.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
+ toggle_status: @cluster.enabled? ? 'true': 'false',
+ cluster_status: @cluster.status_name,
+ cluster_status_reason: @cluster.status_reason } }
+ .col-sm-4
+ = render 'sidebar'
+ .col-sm-8
+ %label.append-bottom-10{ for: 'enable-cluster-integration' }
+ = s_('ClusterIntegration|Enable cluster integration')
+ %p
+ - if @cluster.enabled?
+ - if can?(current_user, :update_cluster, @cluster)
+ = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
+ - else
+ = s_('ClusterIntegration|Cluster integration is enabled for this project.')
+ - else
+ = s_('ClusterIntegration|Cluster integration is disabled for this project.')
+
+ = form_for [@project.namespace.becomes(Namespace), @project, @cluster] do |field|
+ = form_errors(@cluster)
+ .form-group.append-bottom-20
+ %label.append-bottom-10
+ = field.hidden_field :enabled, { class: 'js-toggle-input'}
+
+ %button{ type: 'button',
+ class: "js-toggle-cluster project-feature-toggle #{'checked' unless !@cluster.enabled?} #{'disabled' unless can?(current_user, :update_cluster, @cluster)}",
+ 'aria-label': s_('ClusterIntegration|Toggle Cluster'),
+ disabled: !can?(current_user, :update_cluster, @cluster),
+ data: { 'enabled-text': 'Enabled', 'disabled-text': 'Disabled' } }
+
+ - if can?(current_user, :update_cluster, @cluster)
+ .form-group
+ = field.submit s_('ClusterIntegration|Save'), class: 'btn btn-success'
+
+ - if can?(current_user, :admin_cluster, @cluster)
+ %label.append-bottom-10{ for: 'google-container-engine' }
+ = s_('ClusterIntegration|Google Container Engine')
+ %p
+ - link_gke = link_to(s_('ClusterIntegration|Google Container Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
+
+ .hidden.js-cluster-error.alert.alert-danger.alert-block{ role: 'alert' }
+ = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine')
+ %p.js-error-reason
+
+ .hidden.js-cluster-creating.alert.alert-info.alert-block{ role: 'alert' }
+ = s_('ClusterIntegration|Cluster is being created on Google Container Engine...')
+
+ .hidden.js-cluster-success.alert.alert-success.alert-block{ role: 'alert' }
+ = s_('ClusterIntegration|Cluster was successfully created on Google Container Engine')
+
+ .form_group.append-bottom-20
+ %label.append-bottom-10{ for: 'cluter-name' }
+ = s_('ClusterIntegration|Cluster name')
+ .input-group
+ %input.form-control.cluster-name{ value: @cluster.gcp_cluster_name, disabled: true }
+ %span.input-group-addon.clipboard-addon
+ = clipboard_button(text: @cluster.gcp_cluster_name, title: s_('ClusterIntegration|Copy cluster name'))
+
+ - if can?(current_user, :admin_cluster, @cluster)
+ .well.form_group
+ %label.text-danger
+ = s_('ClusterIntegration|Remove cluster integration')
+ %p
+ = s_('ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your project.')
+ = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure you want to remove cluster integration from this project? This will not delete your cluster on Google Container Engine"})
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index c06e9f323af..71d30da14a9 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -6,18 +6,9 @@
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
- .landing.content-block{ "v-if" => "!isOverviewDialogDismissed" }
- %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box', "@click" => "dismissOverviewDialog()" }
- = icon("times")
- .svg-container
- = custom_icon('icon_cycle_analytics_splash')
- .inner-content
- %h4
- {{ __('Introducing Cycle Analytics') }}
- %p
- {{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
- %p
- = link_to _('Read more'), help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
+ %banner{ "v-if" => "!isOverviewDialogDismissed",
+ "documentation-link": help_page_path('user/project/cycle_analytics'),
+ "v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" }
= icon("spinner spin", "v-show" => "isLoading")
.wrapper{ "v-show" => "!isLoading && !hasError" }
.panel.panel-default
diff --git a/app/views/projects/diffs/_image_diff_frame.html.haml b/app/views/projects/diffs/_image_diff_frame.html.haml
new file mode 100644
index 00000000000..dae73e10460
--- /dev/null
+++ b/app/views/projects/diffs/_image_diff_frame.html.haml
@@ -0,0 +1,5 @@
+- class_name = local_assigns.fetch(:class_name, '')
+- note_type = local_assigns.fetch(:note_type, '')
+
+.frame{ class: class_name, data: { position: position, note_type: note_type } }
+ = image_tag(image_path, alt: alt, draggable: false, lazy: false)
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 56d63250714..1f0ca211074 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -14,20 +14,20 @@
= diff_match_line left.old_pos, nil, text: left.text, view: :parallel
- when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
- %td.line_content.match= left.text
+ %td.line_content.match.left-side= left.text
- else
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
- %td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
+ %td.old_line.diff-line-num.js-avatar-container{ class: left.type, data: { linenumber: left.old_pos } }
= add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id }
- %td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text)
+ %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.text)
- else
%td.old_line.diff-line-num.empty-cell
- %td.line_content.parallel
+ %td.line_content.parallel.left-side
- if right
- case right.type
@@ -35,20 +35,20 @@
= diff_match_line nil, right.new_pos, text: left.text, view: :parallel
- when 'old-nonewline', 'new-nonewline'
%td.new_line.diff-line-num
- %td.line_content.match= right.text
+ %td.line_content.match.right-side= right.text
- else
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
- %td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
+ %td.new_line.diff-line-num.js-avatar-container{ class: right.type, data: { linenumber: right.new_pos } }
= add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id }
- %td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text)
+ %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.text)
- else
%td.old_line.diff-line-num.empty-cell
- %td.line_content.parallel
+ %td.line_content.parallel.right-side
- if discussions_left || discussions_right
= render "discussions/parallel_diff_discussion", discussions_left: discussions_left, discussions_right: discussions_right
diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml
new file mode 100644
index 00000000000..8fc232b464e
--- /dev/null
+++ b/app/views/projects/diffs/_replaced_image_diff.html.haml
@@ -0,0 +1,61 @@
+- blob = diff_file.blob
+- old_blob = diff_file.old_blob
+- blob_raw_path = diff_file_blob_raw_path(diff_file)
+- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file)
+- click_to_comment = local_assigns.fetch(:click_to_comment, true)
+- diff_view_data = local_assigns.fetch(:diff_view_data, '')
+- class_name = ''
+
+- if click_to_comment
+ - class_name = 'js-add-image-diff-note-button click-to-comment'
+
+.image.js-replaced-image{ data: diff_view_data }
+ .two-up.view
+ .wrap
+ .frame.deleted
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
+ %p.image-info.hide
+ %span.meta-filesize= number_to_human_size(old_blob.size)
+ |
+ %strong W:
+ %span.meta-width
+ |
+ %strong H:
+ %span.meta-height
+ .wrap
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path }
+ %p.image-info.hide
+ %span.meta-filesize= number_to_human_size(blob.size)
+ |
+ %strong W:
+ %span.meta-width
+ |
+ %strong H:
+ %span.meta-height
+
+ .swipe.view.hide
+ .swipe-frame
+ .frame.deleted
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
+ .swipe-wrap
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path }
+ %span.swipe-bar
+ %span.top-handle
+ %span.bottom-handle
+
+ .onion-skin.view.hide
+ .onion-skin-frame
+ .frame.deleted
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path }
+ .controls
+ .transparent
+ .drag-track
+ .dragger{ :style => "left: 0px;" }
+ .opaque
+
+.view-modes.hide
+ %ul.view-modes-menu
+ %li.two-up{ data: { mode: 'two-up' } } 2-up
+ %li.swipe{ data: { mode: 'swipe' } } Swipe
+ %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin
diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml
new file mode 100644
index 00000000000..6b0c6bbe48f
--- /dev/null
+++ b/app/views/projects/diffs/_single_image_diff.html.haml
@@ -0,0 +1,16 @@
+- blob = diff_file.blob
+- old_blob = diff_file.old_blob
+- blob_raw_path = diff_file_blob_raw_path(diff_file)
+- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file)
+- click_to_comment = local_assigns.fetch(:click_to_comment, true)
+- diff_view_data = local_assigns.fetch(:diff_view_data, '')
+- class_name = ''
+
+- if click_to_comment
+ - class_name = 'js-add-image-diff-note-button click-to-comment'
+
+.image.js-single-image{ data: diff_view_data }
+ .wrap
+ - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added'
+ = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path }
+ %p.image-info= number_to_human_size(blob.size)
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
index 6b5233833c6..f190073c2fc 100644
--- a/app/views/projects/diffs/viewers/_image.html.haml
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -1,67 +1,13 @@
- diff_file = viewer.diff_file
-- blob = diff_file.blob
-- old_blob = diff_file.old_blob
-- blob_raw_path = diff_file_blob_raw_path(diff_file)
-- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file)
+- image_point = Gitlab::Diff::ImagePoint.new(nil, nil, nil, nil)
+- discussions = @grouped_diff_discussions[diff_file.new_path] if @grouped_diff_discussions
+
+- locals = { diff_file: diff_file, position: diff_file.position(image_point, position_type: :image).to_json, click_to_comment: true, diff_view_data: diff_view_data }
- if diff_file.new_file? || diff_file.deleted_file?
- .image
- %span.wrap
- .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
- = image_tag(blob_raw_path, alt: diff_file.file_path)
- %p.image-info= number_to_human_size(blob.size)
+ = render partial: "projects/diffs/single_image_diff", locals: locals
- else
- .image
- .two-up.view
- %span.wrap
- .frame.deleted
- = image_tag(old_blob_raw_path, alt: diff_file.old_path)
- %p.image-info.hide
- %span.meta-filesize= number_to_human_size(old_blob.size)
- |
- %b W:
- %span.meta-width
- |
- %b H:
- %span.meta-height
- %span.wrap
- .frame.added
- = image_tag(blob_raw_path, alt: diff_file.new_path)
- %p.image-info.hide
- %span.meta-filesize= number_to_human_size(blob.size)
- |
- %b W:
- %span.meta-width
- |
- %b H:
- %span.meta-height
-
- .swipe.view.hide
- .swipe-frame
- .frame.deleted
- = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
- .swipe-wrap
- .frame.added
- = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false)
- %span.swipe-bar
- %span.top-handle
- %span.bottom-handle
-
- .onion-skin.view.hide
- .onion-skin-frame
- .frame.deleted
- = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
- .frame.added
- = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false)
- .controls
- .transparent
- .drag-track
- .dragger{ :style => "left: 0px;" }
- .opaque
-
+ = render partial: "projects/diffs/replaced_image_diff", locals: locals
- .view-modes.hide
- %ul.view-modes-menu
- %li.two-up{ data: { mode: 'two-up' } } 2-up
- %li.swipe{ data: { mode: 'swipe' } } Swipe
- %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin
+.note-container
+ = render partial: "discussions/notes", collection: discussions, as: :discussion
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 0f36e1a7353..e9613534dde 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -9,46 +9,36 @@
%br
Forking a repository allows you to make changes without affecting the original project.
.col-lg-9
- .fork-namespaces
- - if @namespaces.present?
- %label.label-light
- %span
- Click to fork the project to a user or group
- - @namespaces.in_groups_of(6, false) do |group|
- .row
- - group.each do |namespace|
- - avatar = namespace_icon(namespace, 100)
- - if fork = namespace.find_fork_of(@project)
- .fork-thumbnail.forked
- = link_to project_path(fork) do
- - if /no_((\w*)_)*avatar/.match(avatar)
- .no-avatar
- = icon 'question'
- - else
- = image_tag avatar
- .caption
- = namespace.human_name
- - else
- .fork-thumbnail
- = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do
- - if /no_((\w*)_)*avatar/.match(avatar)
- .no-avatar
- = icon 'question'
- - else
- = image_tag avatar
- .caption
- = namespace.human_name
- - else
- %label.label-light
- %span
- No available namespaces to fork the project.
- %br
- %small
- You must have permission to create a project in a namespace before forking.
+ - if @namespaces.present?
+ .fork-thumbnail-container.js-fork-content
+ %h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default
+ Click to fork the project
+ - @namespaces.each do |namespace|
+ - avatar = namespace_icon(namespace, 100)
+ - can_create_project = current_user.can?(:create_projects, namespace)
+ - forked_project = namespace.find_fork_of(@project)
+ - fork_path = forked_project ? project_path(forked_project) : project_forks_path(@project, namespace_key: namespace.id)
+ .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default{ class: [("disabled" unless can_create_project), ("forked" if forked_project)] }
+ = link_to fork_path,
+ method: "POST",
+ class: [("js-fork-thumbnail" unless forked_project), ("disabled has-tooltip" unless can_create_project)],
+ title: (_('You have reached your project limit') unless can_create_project) do
+ - if /no_((\w*)_)*avatar/.match(avatar)
+ = project_identicon(namespace, class: "avatar s100 identicon")
+ - else
+ .avatar-container.s100
+ = image_tag(avatar, class: "avatar s100")
+ %h5.prepend-top-default
+ = namespace.human_name
+ - else
+ %strong
+ No available namespaces to fork the project.
+ %p.prepend-top-default
+ You must have permission to create a project in a namespace before forking.
- .save-project-loader.hide
- .center
- %h2
- %i.fa.fa-spinner.fa-spin
- Forking repository
- %p Please wait a moment, this page will automatically refresh when ready.
+ .save-project-loader.hide.js-fork-content
+ %h2.text-center
+ = icon('spinner spin')
+ Forking repository
+ %p.text-center
+ Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 6a567487514..5f97d31f610 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -2,13 +2,13 @@
%h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request')
%ul.unstyled-list.related-merge-requests
- - has_any_ci = @merge_requests.any?(&:head_pipeline)
+ - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- @merge_requests.each do |merge_request|
%li
%span.merge-request-ci-status
- if merge_request.head_pipeline
= render_pipeline_status(merge_request.head_pipeline)
- - elsif has_any_ci
+ - elsif has_any_head_pipeline
= icon('blank fw')
%span.merge-request-id
= merge_request.to_reference
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
deleted file mode 100644
index 1b7d878c38c..00000000000
--- a/app/views/projects/issues/edit.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues"
-
-%h3.page-title
- Edit Issue ##{@issue.iid}
-%hr
-
-= render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index fbaf88356bf..b9fec8af4d7 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -27,7 +27,9 @@
.issuable-meta
- if @issue.confidential
- = icon('eye-slash', class: 'is-confidential')
+ = icon('eye-slash', class: 'issuable-warning-icon')
+ - if @issue.discussion_locked?
+ = icon('lock', class: 'issuable-warning-icon')
= issuable_meta(@issue, @project, "Issue")
.issuable-actions.js-issuable-actions
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 4a238b99b58..9963cc93633 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -8,7 +8,7 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- - if @all_builds.running_or_pending.any?
+ - if @all_builds.running_or_pending.limit(1).any?
= link_to 'Cancel running', cancel_all_project_jobs_path(@project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index f3c44c94a5c..cb723fe6a18 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -15,6 +15,8 @@
= icon('angle-double-left')
.issuable-meta
+ - if @merge_request.discussion_locked?
+ = icon('lock', class: 'issuable-warning-icon')
= issuable_meta(@merge_request, @project, "Merge request")
.issuable-actions.js-issuable-actions
@@ -29,7 +31,7 @@
- unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request
- %li{ class: merge_request_button_visibility(@merge_request, true) }
+ %li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index d3742f3e4be..d88e3d794d3 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -83,7 +83,7 @@
#pipelines.pipelines.tab-pane
- if @pipelines.any?
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
- #diffs.diffs.tab-pane
+ #diffs.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked? } }
-# This tab is always loaded via AJAX
.mr-loading-status
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index ab87b5e0005..4961835f12a 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -1,6 +1,6 @@
- access = note_max_access_for_user(note)
- if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR)
- %span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project. Handle with care.") }
+ %span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project.") }
= issuable_first_contribution_icon
- if access.nonzero?
%span.note-role.note-role-access= Gitlab::Access.human_access(access)
diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml
deleted file mode 100644
index a0535edafc3..00000000000
--- a/app/views/projects/registry/repositories/_image.html.haml
+++ /dev/null
@@ -1,32 +0,0 @@
-.container-image.js-toggle-container
- .container-image-head
- = link_to "#", class: "js-toggle-button" do
- = icon('chevron-down', 'aria-hidden': 'true')
- = escape_once(image.path)
-
- = clipboard_button(clipboard_text: "docker pull #{image.location}")
-
- - if can?(current_user, :update_container_image, @project)
- .controls.hidden-xs.pull-right
- = link_to project_container_registry_path(@project, image),
- class: 'btn btn-remove has-tooltip',
- title: 'Remove repository',
- data: { confirm: 'Are you sure?' },
- method: :delete do
- = icon('trash cred', 'aria-hidden': 'true')
-
- .container-image-tags.js-toggle-content.hide
- - if image.has_tags?
- .table-holder
- %table.table.tags
- %thead
- %tr
- %th Tag
- %th Tag ID
- %th Size
- %th Created
- - if can?(current_user, :update_container_image, @project)
- %th
- = render partial: 'tag', collection: image.tags
- - else
- .nothing-here-block No tags in Container Registry for this container image.
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 5661af01302..36ea5e013e4 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -1,60 +1,49 @@
- page_title "Container Registry"
-.row.prepend-top-default.append-bottom-default
- .col-lg-3
- %h4.prepend-top-0
+%section
+ .settings-header
+ %h4
= page_title
%p
- With the Docker Container Registry integrated into GitLab, every project
- can have its own space to store its Docker images.
+ = s_('ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images.')
%p.append-bottom-0
= succeed '.' do
- Learn more about
- = link_to 'Container Registry', help_page_path('user/project/container_registry'), target: '_blank'
+ = s_('ContainerRegistry|Learn more about')
+ = link_to _('Container Registry'), help_page_path('user/project/container_registry'), target: '_blank'
+ .row.registry-placeholder.prepend-bottom-10
+ .col-lg-12
+ #js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json) } }
- .col-lg-9
- .panel.panel-default
- .panel-heading
- %h4.panel-title
- How to use the Container Registry
- .panel-body
- %p
- First log in to GitLab&rsquo;s Container Registry using your GitLab username
- and password. If you have
- = link_to '2FA enabled', help_page_path('user/profile/account/two_factor_authentication'), target: '_blank'
- you need to use a
- = succeed ':' do
- = link_to 'personal access token', help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank'
- %pre
- docker login #{Gitlab.config.registry.host_port}
- %br
- %p
- Once you log in, you&rsquo;re free to create and upload a container image
- using the common
- %code build
- and
- %code push
- commands:
- %pre
- :plain
- docker build -t #{escape_once(@project.container_registry_url)} .
- docker push #{escape_once(@project.container_registry_url)}
+ = page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('registry_list')
- %hr
- %h5.prepend-top-default
- Use different image names
- %p.light
- GitLab supports up to 3 levels of image names. The following
- examples of images are valid for your project:
- %pre
- :plain
- #{escape_once(@project.container_registry_url)}:tag
- #{escape_once(@project.container_registry_url)}/optional-image-name:tag
- #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag
-
- - if @images.blank?
- %p.settings-message.text-center.append-bottom-default
- No container images stored for this project. Add one by following the
- instructions above.
- - else
- = render partial: 'image', collection: @images
+ .row.prepend-top-10
+ .col-lg-12
+ .panel.panel-default
+ .panel-heading
+ %h4.panel-title
+ = s_('ContainerRegistry|How to use the Container Registry')
+ .panel-body
+ %p
+ - link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
+ - link_2fa = link_to(_('2FA enabled'), help_page_path('user/profile/account/two_factor_authentication'), target: '_blank')
+ = s_('ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:').html_safe % { link_2fa: link_2fa, link_token: link_token }
+ %pre
+ docker login #{Gitlab.config.registry.host_port}
+ %br
+ %p
+ = s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe }
+ %pre
+ :plain
+ docker build -t #{escape_once(@project.container_registry_url)} .
+ docker push #{escape_once(@project.container_registry_url)}
+ %hr
+ %h5.prepend-top-default
+ = s_('ContainerRegistry|Use different image names')
+ %p.light
+ = s_('ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:')
+ %pre
+ :plain
+ #{escape_once(@project.container_registry_url)}:tag
+ #{escape_once(@project.container_registry_url)}/optional-image-name:tag
+ #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index b842fd57cf3..c0b1c62e8ef 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -23,7 +23,7 @@
- disabled_class = 'disabled'
- disabled_title = @service.disabled_title
- = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index fda068f08c2..7062c5b765e 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,5 +1,5 @@
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
-- add_to_breadcrumbs "Snippets", dashboard_snippets_path
+- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 468ab922542..1927216e191 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,12 +2,11 @@
- release = @releases.find { |release| release.tag == tag.name }
%li.flex-row
.row-main-content.str-truncated
- = link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
- = icon('tag')
- = tag.name
+ = icon('tag')
+ = link_to tag.name, project_tag_path(@project, tag.name), class: 'item-title ref-name prepend-left-4'
- if protected_tag?(@project, tag)
- %span.label.label-success
+ %span.label.label-success.prepend-left-4
protected
- if tag.message.present?
diff --git a/app/views/projects/tree/_old_tree_content.html.haml b/app/views/projects/tree/_old_tree_content.html.haml
index 820b947804e..6ea78851b8d 100644
--- a/app/views/projects/tree/_old_tree_content.html.haml
+++ b/app/views/projects/tree/_old_tree_content.html.haml
@@ -6,7 +6,7 @@
%th= s_('ProjectFileTree|Name')
%th.hidden-xs
.pull-left= _('Last commit')
- %th.text-right= _('Last Update')
+ %th.text-right= _('Last update')
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 853e2a6e7ec..6cddc38d11a 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,10 +1,6 @@
.tree-ref-container
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
- - if show_new_repo?
- .tree-ref-target-holder.js-tree-ref-target-holder
- = icon('long-arrow-right', title: 'to target branch')
- = render 'shared/target_switcher', destination: 'tree', path: @path
- unless show_new_repo?
= render 'projects/tree/old_tree_header'
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index f819f2addaa..0cc6674842a 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -12,7 +12,7 @@
= webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- - if show_auto_devops_callout?(@project)
+ - if show_auto_devops_callout?(@project) && !show_new_repo?
= render 'shared/auto_devops_callout'
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index e5a1fccf9ba..4e265bf733a 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,5 @@
-- commit_message = @page.persisted? ? "Update #{@page.title}" : "Create #{@page.title}"
+- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
+- commit_message = commit_message % { page_title: @page.title }
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
@@ -12,13 +13,13 @@
.form-group
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
- = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
+ = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control'
.form-group
.col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-12
= render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do
- = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
+ = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: s_("WikiPage|Write your content or drag files here...")
= render 'shared/notes/hints'
.clearfix
@@ -26,12 +27,11 @@
.help-block
= succeed '.' do
- To link to a (new) page, simply type
- %code [Link Title](page-slug)
+ = (s_("WikiMarkdownTip|To link to a (new) page, simply type %{link_example}") % { link_example: '<code>[Link Title](page-slug)</code>' }).html_safe
= succeed '.' do
- More examples are in the
- = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
+ - markdown_link = link_to s_("WikiMarkdownDocs|documentation"), help_page_path('user/markdown', anchor: 'wiki-specific-markdown')
+ = (s_("WikiMarkdownDocs|More examples are in the %{docs_link}") % { docs_link: markdown_link }).html_safe
.form-group
.col-sm-12= f.label :commit_message, class: 'control-label-full-width'
@@ -39,10 +39,10 @@
.form-actions
- if @page && @page.persisted?
- = f.submit 'Save changes', class: "btn-save btn"
+ = f.submit _("Save changes"), class: 'btn-save btn'
.pull-right
- = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped"
+ = link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped'
- else
- = f.submit 'Create page', class: "btn-create btn"
+ = f.submit s_("Wiki|Create page"), class: 'btn-create btn'
.pull-right
- = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
+ = link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel'
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 3bbd8042c3a..cadda0a33c2 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,9 @@
- if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- New page
+ = s_("Wiki|New page")
= link_to project_wiki_history_path(@project, @page), class: "btn" do
- Page history
+ = s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @project) && @page.latest?
= link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
- Edit
+ = _("Edit")
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 13dd8461433..06a3cac12d5 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -3,16 +3,15 @@
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
- %h3.page-title New Wiki Page
+ %h3.page-title= s_("WikiNewPageTitle|New Wiki Page")
.modal-body
%form.new-wiki-page
.form-group
= label_tag :new_wiki_path do
- %span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
+ %span= s_("WikiPage|Page slug")
+ = text_field_tag :new_wiki_path, nil, placeholder: s_("WikiNewPagePlaceholder|how-to-setup"), class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
%span.new-wiki-page-slug-tip
= icon('lightbulb-o')
- Tip: You can specify the full path for the new file.
- We will automatically create any missing directories.
+ = s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
.form-actions
- = button_tag 'Create page', class: 'build-new-wiki btn btn-create'
+ = button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-create"
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index 7c2f562d422..0a1ccbc5f1c 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -2,4 +2,4 @@
= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+ %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.commit.authored_date) }).html_safe
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index f7283ae4739..5b781294d68 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -8,7 +8,7 @@
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
- Clone repository
+ = _("Clone repository")
.blocks-container
.block.block-first
@@ -17,6 +17,6 @@
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
- More Pages
+ = s_("Wiki|More Pages")
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 8fd60216536..0d77e5bd16d 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,11 +1,10 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
-- page_title "Edit", @page.title.capitalize, "Wiki"
+- page_title _("Edit"), @page.title.capitalize, _("Wiki")
- if @conflict
.alert.alert-danger
- Someone edited the page the same time you did. Please check out
- = link_to "the page", project_wiki_path(@project, @page), target: "_blank"
- and make sure your changes will not unintentionally remove theirs.
+ - page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
+ = (s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
@@ -20,20 +19,20 @@
%span.light
&middot;
- if @page.persisted?
- Edit Page
+ = s_("Wiki|Edit Page")
- else
- Create Page
+ = s_("Wiki|Create Page")
.nav-controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- New page
+ = s_("Wiki|New page")
- if @page.persisted?
= link_to project_wiki_history_path(@project, @page), class: "btn" do
- Page history
+ = s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project)
- = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
- Delete
+ = link_to project_wiki_path(@project, @page), data: { confirm: s_("WikiPageConfirmDelete|Are you sure you want to delete this page?")}, method: :delete, class: "btn btn-danger" do
+ = _("Delete")
= render 'form'
diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml
index 7dfa405d063..911e1339541 100644
--- a/app/views/projects/wikis/empty.html.haml
+++ b/app/views/projects/wikis/empty.html.haml
@@ -1,6 +1,6 @@
-- page_title "Wiki"
+- page_title _("Wiki")
-%h3.page-title Empty page
+%h3.page-title= _("Wiki|Empty page")
%hr
.error_message
- You are not allowed to create wiki pages
+ = s_("WikiEmptyPageError|You are not allowed to create wiki pages")
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index e740fb93ea4..10dbbc0e42c 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,36 +1,34 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
-- page_title "Git Access", "Wiki"
+- page_title s_("WikiClone|Git Access"), _("Wiki")
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
.git-access-header
- Clone repository
+ = _("Clone repository")
%strong= @project_wiki.full_path
= render "shared/clone_panel", project: @project_wiki
.wiki-git-access
- %h3 Install Gollum
+ %h3= s_("WikiClone|Install Gollum")
%pre.dark
:preserve
gem install gollum
%p
- It is recommended to install
- %code github-markdown
- so that GFM features render locally:
+ = (s_("WikiClone|It is recommended to install %{markdown} so that GFM features render locally:") % { markdown: "<code>github-markdown</code>" }).html_safe
%pre.dark
:preserve
gem install github-markdown
- %h3 Clone your wiki
+ %h3= s_("WikiClone|Clone your wiki")
%pre.dark
:preserve
git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')}
cd #{h @project_wiki.path}
- %h3 Start Gollum and edit locally
+ %h3= s_("WikiClone|Start Gollum and edit locally")
%pre.dark
:preserve
gollum
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 306feeff259..9ee09262324 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,4 +1,4 @@
-- page_title "History", @page.title.capitalize, "Wiki"
+- page_title _("History"), @page.title.capitalize, _("Wiki")
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
@@ -9,17 +9,17 @@
= link_to @page.title.capitalize, project_wiki_path(@project, @page)
%span.light
&middot;
- History
+ = _("History")
.table-holder
%table.table
%thead
%tr
- %th Page version
- %th Author
- %th Commit Message
- %th Last updated
- %th Format
+ %th= s_("Wiki|Page version")
+ %th= _("Author")
+ %th= _("Commit Message")
+ %th= _("Last updated")
+ %th= _("Format")
%tbody
- @page.versions.each_with_index do |version, index|
- commit = version
@@ -29,13 +29,13 @@
commit.id, index == 0) do
= truncate_sha(commit.id)
%td
- = commit.author.name
+ = commit.author_name
%td
= commit.message
%td
#{time_ago_with_tooltip(version.authored_date)}
%td
%strong
- = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+ = version.format
= render 'sidebar'
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index d533c611a38..aeef64fd7eb 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,19 +1,19 @@
- @no_container = true
- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
-- breadcrumb_title "Pages"
-- page_title "Pages", "Wiki"
+- breadcrumb_title s_("Wiki|Pages")
+- page_title s_("Wiki|Pages"), _("Wiki")
%div{ class: container_class }
.wiki-page-header
.nav-text
%h2.wiki-page-title
- Wiki Pages
+ = s_("Wiki|Wiki Pages")
.nav-controls
= link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
- Clone repository
+ = _("Clone repository")
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index b066a812ec8..de15fc99eda 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,8 +1,8 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- breadcrumb_title @page.title.capitalize
- wiki_breadcrumb_dropdown_links(@page.slug)
-- page_title @page.title.capitalize, "Wiki"
-- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
+- page_title @page.title.capitalize, _("Wiki")
+- add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project)
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
@@ -11,9 +11,7 @@
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
- Last edited by
- %strong
- #{@page.commit.author.name}
+ = (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls
@@ -21,8 +19,10 @@
- if @page.historical?
.warning_message
- This is an old version of this page.
- You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}.
+ = s_("WikiHistoricalPage|This is an old version of this page.")
+ - most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page)
+ - history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page)
+ = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml
index 2f09c2fec87..7c633175a06 100644
--- a/app/views/shared/_auto_devops_callout.html.haml
+++ b/app/views/shared/_auto_devops_callout.html.haml
@@ -6,10 +6,10 @@
.svg-container
= custom_icon('icon_autodevops')
.user-callout-copy
- %h4= _('Auto DevOps (Beta)')
- %p= _('Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
+ %h4= s_('AutoDevOps|Auto DevOps (Beta)')
+ %p= s_('AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- #{s_('AutoDevOps|Learn more in the')}
- = link_to _('Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
+ - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
- = link_to _('Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout'
+ = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout'
diff --git a/app/views/profiles/gpg_keys/_email_with_badge.html.haml b/app/views/shared/_email_with_badge.html.haml
index 5f7844584e1..b7bbc109238 100644
--- a/app/views/profiles/gpg_keys/_email_with_badge.html.haml
+++ b/app/views/shared/_email_with_badge.html.haml
@@ -2,7 +2,7 @@
- css_classes << (verified ? 'verified': 'unverified')
- text = verified ? 'Verified' : 'Unverified'
-.gpg-email-badge
- .gpg-email-badge-email= email
+.email-badge
+ .email-badge-email= email
%div{ class: css_classes }
= text
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index e415ec64c38..b8b1f4ca42f 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -1,9 +1,9 @@
- type = impersonation ? "impersonation" : "personal access"
%h5.prepend-top-0
- Add a #{type} Token
+ Add a #{type} token
%p.profile-settings-content
- Pick a name for the application, and we'll give you a unique #{type} Token.
+ Pick a name for the application, and we'll give you a unique #{type} token.
= form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
index eb5ddb0dde4..2530db986e0 100644
--- a/app/views/shared/_sidebar_toggle_button.html.haml
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -1,8 +1,8 @@
%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
- = icon('angle-double-left')
- = icon('angle-double-right')
+ = sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
+ = sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
%span.collapse-text Collapse sidebar
= button_tag class: 'close-nav-button', type: 'button' do
- = icon ('times')
+ = sprite_icon('close', size: 16)
%span.collapse-text Close sidebar
diff --git a/app/views/shared/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml
deleted file mode 100644
index bbe9692a7da..00000000000
--- a/app/views/shared/_target_switcher.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag nil, method: :get, class: "project-refs-form project-refs-target-form" do
- = hidden_field_tag :destination, destination
- - if defined?(path)
- = hidden_field_tag :path, path
- - @options && @options.each do |key, value|
- = hidden_field_tag key, value, id: nil
- .dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" }
- .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
- = dropdown_title _("Create a new branch")
- = dropdown_input _("Create a new branch")
- = dropdown_title _("Select existing branch"), options: {close: false}
- = dropdown_filter _("Search branches and tags")
- = dropdown_content
- = dropdown_loading
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 7e22c678e81..ee8ad8e3999 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -12,11 +12,11 @@
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
-.hidden-xs.hidden-sm
- = render 'shared/issuable/search_bar', type: :boards
+#board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
+ .hidden-xs.hidden-sm
+ = render 'shared/issuable/search_bar', type: :boards
-#board-app.boards-app{ "v-cloak" => true, data: board_data }
- .boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
+ .boards-list
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
%board{ "v-cloak" => true,
diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml
index 3baa956b910..639f28cc210 100644
--- a/app/views/shared/builds/_tabs.html.haml
+++ b/app/views/shared/builds/_tabs.html.haml
@@ -3,22 +3,22 @@
= link_to build_path_proc.call(nil) do
All
%span.badge.js-totalbuilds-count
- = number_with_delimiter(all_builds.count(:id))
+ = limited_counter_with_delimiter(all_builds)
%li{ class: active_when(scope == 'pending') }>
= link_to build_path_proc.call('pending') do
Pending
%span.badge
- = number_with_delimiter(all_builds.pending.count(:id))
+ = limited_counter_with_delimiter(all_builds.pending)
%li{ class: active_when(scope == 'running') }>
= link_to build_path_proc.call('running') do
Running
%span.badge
- = number_with_delimiter(all_builds.running.count(:id))
+ = limited_counter_with_delimiter(all_builds.running)
%li{ class: active_when(scope == 'finished') }>
= link_to build_path_proc.call('finished') do
Finished
%span.badge
- = number_with_delimiter(all_builds.finished.count(:id))
+ = limited_counter_with_delimiter(all_builds.finished)
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index f16bc8dd430..9ef015047c9 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -3,9 +3,9 @@
- button_method = issuable_close_reopen_button_method(issuable)
- if can_update && is_current_user
- = link_to "Close #{display_issuable_type}", close_issuable_url(issuable), method: button_method,
+ = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- = link_to "Reopen #{display_issuable_type}", reopen_issuable_url(issuable), method: button_method,
+ = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
index a38cd319e3c..39a5171c1d6 100644
--- a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
+++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
@@ -7,7 +7,7 @@
- button_method = issuable_close_reopen_button_method(issuable)
.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
- = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_url(issuable),
+ = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
@@ -16,7 +16,7 @@
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
- data: { text: "Close #{display_issuable_type}", url: close_issuable_url(issuable),
+ data: { text: "Close #{display_issuable_type}", url: close_issuable_path(issuable),
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
@@ -26,7 +26,7 @@
= display_issuable_type
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
- data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_url(issuable),
+ data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_path(issuable),
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index 8a71819aa8e..d2b62557e03 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -11,7 +11,7 @@
.hide-collapsed.participants-list
- participants.each do |participant|
.participants-author.js-participants-author
- = link_to_member(@project, participant, name: false, size: 24)
+ = link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
- if participants_extra > 0
.hide-collapsed.participants-more
%a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 674f13ddb23..7b7411b1e23 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -119,6 +119,10 @@
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point
+ - if issuable.has_attribute?(:discussion_locked)
+ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe
+ #js-lock-entry-point
+
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
- subscribed = issuable.subscribed?(current_user, @project)
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 1dfe380db16..4b9af78bc1a 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -7,7 +7,7 @@
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
= icon('caret-down', class: 'toggle-icon')
- %ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
+ %ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 725bf916592..71c0d740bc8 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -24,20 +24,21 @@
-# DiffNote
= f.hidden_field :position
- = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
- = render 'projects/zen', f: f,
- attr: :note,
- classes: 'note-textarea js-note-text',
- placeholder: "Write a comment or drag your files here...",
- supports_quick_actions: supports_quick_actions,
- supports_autocomplete: supports_autocomplete
- = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
- .error-alert
-
- .note-form-actions.clearfix
- = render partial: 'shared/notes/comment_button'
-
- = yield(:note_actions)
-
- %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
- Discard draft
+ .discussion-form-container
+ = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
+ = render 'projects/zen', f: f,
+ attr: :note,
+ classes: 'note-textarea js-note-text',
+ placeholder: "Write a comment or drag your files here...",
+ supports_quick_actions: supports_quick_actions,
+ supports_autocomplete: supports_autocomplete
+ = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
+ .error-alert
+
+ .note-form-actions.clearfix
+ = render partial: 'shared/notes/comment_button'
+
+ = yield(:note_actions)
+
+ %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
+ Discard draft
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 4f00a9f2759..b6085fd3af0 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -1,7 +1,10 @@
- return unless note.author
- return if note.cross_reference_not_visible_for?(current_user)
+- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
- note_editable = note_editable?(note)
+- note_counter = local_assigns.fetch(:note_counter, 0)
+
%li.timeline-entry{ id: dom_id(note),
class: ["note", "note-row-#{note.id}", ('system-note' if note.system)],
data: { author_id: note.author.id,
@@ -12,8 +15,18 @@
- if note.system
= icon_for_system_note(note)
- else
- %a{ href: user_path(note.author) }
+ %a.image-diff-avatar-link{ href: user_path(note.author) }
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
+ - if note.is_a?(DiffNote) && note.on_image?
+ - if show_image_comment_badge && note_counter == 0
+ -# Only show this for the first comment in the discussion
+ %span.image-comment-badge.inverted
+ = icon('comment-o')
+ - elsif note_counter == 0
+ - counter = badge_counter if local_assigns[:badge_counter]
+ - badge_class = "hidden" if @fresh_discussion || counter.nil?
+ %span.badge{ class: badge_class }
+ = counter
.timeline-content
.note-header
.note-header-info
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index e3e86709b8f..c6e18108c7a 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -1,3 +1,6 @@
+- issuable = @issue || @merge_request
+- discussion_locked = issuable&.discussion_locked?
+
%ul#notes-list.notes.main-notes-list.timeline
= render "shared/notes/notes"
@@ -21,5 +24,14 @@
or
= link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link'
to comment
-
+- elsif discussion_locked
+ .disabled-comment.text-center.prepend-top-default
+ %span.issuable-note-warning
+ %span.icon= sprite_icon('lock', size: 14)
+ %span
+ This
+ = issuable.class.to_s.titleize.downcase
+ is locked. Only
+ %b project members
+ can comment.
%script.js-notes-data{ type: "application/json" }= initial_notes_data(autocomplete).to_json.html_safe
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index d0ffcc88d43..6c3cd6ecefe 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,7 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block.user-cover-block.layout-nav
+ .cover-block.user-cover-block.top-area
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-gray has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index e2a1b3dcc41..52e7d346e74 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -6,6 +6,7 @@ class BuildFinishedWorker
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
+ BuildTraceSectionsWorker.perform_async(build.id)
BuildCoverageWorker.new.perform(build.id)
BuildHooksWorker.new.perform(build.id)
end
diff --git a/app/workers/build_trace_sections_worker.rb b/app/workers/build_trace_sections_worker.rb
new file mode 100644
index 00000000000..8c57e8f767b
--- /dev/null
+++ b/app/workers/build_trace_sections_worker.rb
@@ -0,0 +1,8 @@
+class BuildTraceSectionsWorker
+ include Sidekiq::Worker
+ include PipelineQueue
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id)&.parse_trace_sections!
+ end
+end
diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb
new file mode 100644
index 00000000000..63300b58a25
--- /dev/null
+++ b/app/workers/cluster_provision_worker.rb
@@ -0,0 +1,10 @@
+class ClusterProvisionWorker
+ include Sidekiq::Worker
+ include ClusterQueue
+
+ def perform(cluster_id)
+ Gcp::Cluster.find_by_id(cluster_id).try do |cluster|
+ Ci::ProvisionClusterService.new.execute(cluster)
+ end
+ end
+end
diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb
new file mode 100644
index 00000000000..a5074d13220
--- /dev/null
+++ b/app/workers/concerns/cluster_queue.rb
@@ -0,0 +1,10 @@
+##
+# Concern for setting Sidekiq settings for the various Gcp clusters workers.
+#
+module ClusterQueue
+ extend ActiveSupport::Concern
+
+ included do
+ sidekiq_options queue: :gcp_cluster
+ end
+end
diff --git a/app/workers/project_migrate_hashed_storage_worker.rb b/app/workers/project_migrate_hashed_storage_worker.rb
new file mode 100644
index 00000000000..ca276d7801c
--- /dev/null
+++ b/app/workers/project_migrate_hashed_storage_worker.rb
@@ -0,0 +1,11 @@
+class ProjectMigrateHashedStorageWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform(project_id)
+ project = Project.find_by(id: project_id)
+ return if project.nil? || project.pending_delete?
+
+ ::Projects::HashedStorageMigrationService.new(project, logger).execute
+ end
+end
diff --git a/app/workers/storage_migrator_worker.rb b/app/workers/storage_migrator_worker.rb
new file mode 100644
index 00000000000..b48ead799b9
--- /dev/null
+++ b/app/workers/storage_migrator_worker.rb
@@ -0,0 +1,30 @@
+class StorageMigratorWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ BATCH_SIZE = 100
+
+ def perform(start, finish)
+ projects = build_relation(start, finish)
+
+ projects.with_route.find_each(batch_size: BATCH_SIZE) do |project|
+ Rails.logger.info "Starting storage migration of #{project.full_path} (ID=#{project.id})..."
+
+ begin
+ project.migrate_to_hashed_storage!
+ rescue => err
+ Rails.logger.error("#{err.message} migrating storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
+ end
+ end
+ end
+
+ def build_relation(start, finish)
+ relation = Project
+ table = Project.arel_table
+
+ relation = relation.where(table[:id].gteq(start)) if start
+ relation = relation.where(table[:id].lteq(finish)) if finish
+
+ relation
+ end
+end
diff --git a/app/workers/wait_for_cluster_creation_worker.rb b/app/workers/wait_for_cluster_creation_worker.rb
new file mode 100644
index 00000000000..5aa3bbdaa9d
--- /dev/null
+++ b/app/workers/wait_for_cluster_creation_worker.rb
@@ -0,0 +1,27 @@
+class WaitForClusterCreationWorker
+ include Sidekiq::Worker
+ include ClusterQueue
+
+ INITIAL_INTERVAL = 2.minutes
+ EAGER_INTERVAL = 10.seconds
+ TIMEOUT = 20.minutes
+
+ def perform(cluster_id)
+ Gcp::Cluster.find_by_id(cluster_id).try do |cluster|
+ Ci::FetchGcpOperationService.new.execute(cluster) do |operation|
+ case operation.status
+ when 'RUNNING'
+ if TIMEOUT < Time.now.utc - operation.start_time.to_time.utc
+ return cluster.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}")
+ end
+
+ WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, cluster.id)
+ when 'DONE'
+ Ci::FinalizeClusterCreationService.new.execute(cluster)
+ else
+ return cluster.make_errored!("Unexpected operation status; #{operation.status} #{operation.status_message}")
+ end
+ end
+ end
+ end
+end