summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-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/illustrations/image_comment_light_cursor.svg1
-rw-r--r--app/assets/images/illustrations/image_comment_light_cursor@2x.svg1
-rw-r--r--app/assets/javascripts/activities.js10
-rw-r--r--app/assets/javascripts/admin.js117
-rw-r--r--app/assets/javascripts/aside.js24
-rw-r--r--app/assets/javascripts/behaviors/secret_values.js42
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js3
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js4
-rw-r--r--app/assets/javascripts/blob/blob_line_permalink_updater.js4
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js2
-rw-r--r--app/assets/javascripts/commit/image_file.js4
-rw-r--r--app/assets/javascripts/commits.js5
-rw-r--r--app/assets/javascripts/compare.js37
-rw-r--r--app/assets/javascripts/compare_autocomplete.js116
-rw-r--r--app/assets/javascripts/contextual_sidebar.js10
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue3
-rw-r--r--app/assets/javascripts/diff.js8
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js3
-rw-r--r--app/assets/javascripts/dispatcher.js56
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js3
-rw-r--r--app/assets/javascripts/fly_out_nav.js2
-rw-r--r--app/assets/javascripts/gl_dropdown.js3
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js21
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js17
-rw-r--r--app/assets/javascripts/groups/components/app.vue4
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue3
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue14
-rw-r--r--app/assets/javascripts/groups/new_group_child.js5
-rw-r--r--app/assets/javascripts/image_diff/helpers/badge_helper.js7
-rw-r--r--app/assets/javascripts/init_issuable_sidebar.js4
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js2
-rw-r--r--app/assets/javascripts/issue.js15
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue94
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue24
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue2
-rw-r--r--app/assets/javascripts/issue_show/index.js2
-rw-r--r--app/assets/javascripts/job.js12
-rw-r--r--app/assets/javascripts/lib/utils/cache.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js3
-rw-r--r--app/assets/javascripts/lib/utils/constants.js1
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js218
-rw-r--r--app/assets/javascripts/lib/utils/tick_formats.js39
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js126
-rw-r--r--app/assets/javascripts/line_highlighter.js2
-rw-r--r--app/assets/javascripts/locale/index.js15
-rw-r--r--app/assets/javascripts/main.js38
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js3
-rw-r--r--app/assets/javascripts/merge_request.js263
-rw-r--r--app/assets/javascripts/merge_request_tabs.js648
-rw-r--r--app/assets/javascripts/milestone_select.js3
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue24
-rw-r--r--app/assets/javascripts/monitoring/components/graph/deployment.vue101
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js2
-rw-r--r--app/assets/javascripts/monitoring/utils/date_time_formatters.js1
-rw-r--r--app/assets/javascripts/namespace_select.js4
-rw-r--r--app/assets/javascripts/notes.js8
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue (renamed from app/assets/javascripts/notes/components/issue_comment_form.vue)2
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue (renamed from app/assets/javascripts/notes/components/issue_note_body.vue)6
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue (renamed from app/assets/javascripts/notes/components/issue_note_form.vue)0
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue (renamed from app/assets/javascripts/notes/components/issue_discussion.vue)12
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue (renamed from app/assets/javascripts/notes/components/issue_note.vue)13
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue (renamed from app/assets/javascripts/notes/components/issue_notes_app.vue)25
-rw-r--r--app/assets/javascripts/notes/index.js16
-rw-r--r--app/assets/javascripts/notifications_dropdown.js48
-rw-r--r--app/assets/javascripts/notifications_form.js93
-rw-r--r--app/assets/javascripts/pager.js136
-rw-r--r--app/assets/javascripts/performance_bar.js3
-rw-r--r--app/assets/javascripts/pipelines/components/empty_state.vue47
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue41
-rw-r--r--app/assets/javascripts/pipelines/pipelines_bundle.js3
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue8
-rw-r--r--app/assets/javascripts/project.js3
-rw-r--r--app/assets/javascripts/project_find_file.js298
-rw-r--r--app/assets/javascripts/project_variables.js39
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js4
-rw-r--r--app/assets/javascripts/render_gfm.js4
-rw-r--r--app/assets/javascripts/repo/components/new_dropdown/modal.vue8
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue16
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue6
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue5
-rw-r--r--app/assets/javascripts/repo/stores/actions.js3
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js3
-rw-r--r--app/assets/javascripts/right_sidebar.js438
-rw-r--r--app/assets/javascripts/search.js207
-rw-r--r--app/assets/javascripts/search_autocomplete.js769
-rw-r--r--app/assets/javascripts/shortcuts.js5
-rw-r--r--app/assets/javascripts/shortcuts_blob.js6
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js24
-rw-r--r--app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue9
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js71
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js5
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js4
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js50
-rw-r--r--app/assets/javascripts/single_file_diff.js3
-rw-r--r--app/assets/javascripts/syntax_highlight.js14
-rw-r--r--app/assets/javascripts/todos.js4
-rw-r--r--app/assets/javascripts/tree.js5
-rw-r--r--app/assets/javascripts/users/activity_calendar.js5
-rw-r--r--app/assets/javascripts/users/user_tabs.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue (renamed from app/assets/javascripts/vue_shared/components/popup_dialog.vue)10
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue85
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue36
-rw-r--r--app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js36
-rw-r--r--app/assets/javascripts/vue_shared/mixins/timeago.js6
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/contextual-sidebar.scss41
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss301
-rw-r--r--app/assets/stylesheets/framework/files.scss5
-rw-r--r--app/assets/stylesheets/framework/filters.scss59
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/images.scss7
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss6
-rw-r--r--app/assets/stylesheets/framework/layout.scss5
-rw-r--r--app/assets/stylesheets/framework/lists.scss2
-rw-r--r--app/assets/stylesheets/framework/mobile.scss17
-rw-r--r--app/assets/stylesheets/framework/modal.scss18
-rw-r--r--app/assets/stylesheets/framework/secondary-navigation-elements.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss9
-rw-r--r--app/assets/stylesheets/framework/toggle.scss64
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/pages/boards.scss8
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss21
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/environments.scss21
-rw-r--r--app/assets/stylesheets/pages/issuable.scss31
-rw-r--r--app/assets/stylesheets/pages/issues.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/assets/stylesheets/pages/members.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/notifications.scss4
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss6
-rw-r--r--app/assets/stylesheets/pages/repo.scss13
-rw-r--r--app/assets/stylesheets/pages/search.scss7
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/admin/groups_controller.rb8
-rw-r--r--app/controllers/admin/health_check_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb11
-rw-r--r--app/controllers/concerns/boards_responses.rb4
-rw-r--r--app/controllers/concerns/creates_commit.rb26
-rw-r--r--app/controllers/concerns/group_tree.rb2
-rw-r--r--app/controllers/concerns/issuable_actions.rb22
-rw-r--r--app/controllers/concerns/issuable_collections.rb15
-rw-r--r--app/controllers/concerns/issues_action.rb2
-rw-r--r--app/controllers/concerns/members_presentation.rb11
-rw-r--r--app/controllers/concerns/merge_requests_action.rb2
-rw-r--r--app/controllers/concerns/milestone_actions.rb8
-rw-r--r--app/controllers/concerns/notes_actions.rb32
-rw-r--r--app/controllers/concerns/oauth_applications.rb2
-rw-r--r--app/controllers/concerns/preview_markdown.rb2
-rw-r--r--app/controllers/concerns/renders_commits.rb2
-rw-r--r--app/controllers/concerns/renders_notes.rb2
-rw-r--r--app/controllers/concerns/service_params.rb2
-rw-r--r--app/controllers/concerns/snippets_actions.rb2
-rw-r--r--app/controllers/concerns/spammable_actions.rb24
-rw-r--r--app/controllers/concerns/toggle_subscription_action.rb2
-rw-r--r--app/controllers/concerns/with_performance_bar.rb1
-rw-r--r--app/controllers/groups/group_members_controller.rb9
-rw-r--r--app/controllers/health_controller.rb11
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb4
-rw-r--r--app/controllers/projects/clusters_controller.rb7
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb5
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/project_members_controller.rb8
-rw-r--r--app/controllers/projects/tree_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb3
-rw-r--r--app/helpers/application_settings_helper.rb19
-rw-r--r--app/helpers/blob_helper.rb14
-rw-r--r--app/helpers/builds_helper.rb3
-rw-r--r--app/helpers/diff_helper.rb16
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb7
-rw-r--r--app/helpers/members_helper.rb7
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/storage_health_helper.rb6
-rw-r--r--app/mailers/emails/notes.rb10
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/blob.rb12
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/commit.rb14
-rw-r--r--app/models/concerns/cache_markdown_field.rb3
-rw-r--r--app/models/concerns/mentionable.rb10
-rw-r--r--app/models/concerns/milestoneish.rb8
-rw-r--r--app/models/concerns/noteable.rb2
-rw-r--r--app/models/concerns/participable.rb12
-rw-r--r--app/models/concerns/relative_positioning.rb8
-rw-r--r--app/models/concerns/resolvable_discussion.rb14
-rw-r--r--app/models/concerns/routable.rb10
-rw-r--r--app/models/concerns/spammable.rb5
-rw-r--r--app/models/concerns/taskable.rb2
-rw-r--r--app/models/concerns/time_trackable.rb18
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/identity.rb4
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/member.rb1
-rw-r--r--app/models/merge_request.rb35
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/namespace.rb11
-rw-r--r--app/models/note.rb13
-rw-r--r--app/models/personal_access_token.rb21
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/redirect_route.rb28
-rw-r--r--app/models/repository.rb34
-rw-r--r--app/models/route.rb26
-rw-r--r--app/models/user.rb21
-rw-r--r--app/models/user_synced_attributes_metadata.rb10
-rw-r--r--app/presenters/group_member_presenter.rb15
-rw-r--r--app/presenters/member_presenter.rb38
-rw-r--r--app/presenters/members_presenter.rb15
-rw-r--r--app/presenters/project_member_presenter.rb15
-rw-r--r--app/serializers/concerns/with_pagination.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb27
-rw-r--r--app/services/ci/register_job_service.rb17
-rw-r--r--app/services/concerns/issues/resolve_discussions.rb16
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/members/approve_access_request_service.rb11
-rw-r--r--app/services/members/destroy_service.rb11
-rw-r--r--app/services/spam_check_service.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml18
-rw-r--r--app/views/admin/system_info/show.html.haml6
-rw-r--r--app/views/admin/users/show.html.haml1
-rw-r--r--app/views/ci/variables/_index.html.haml6
-rw-r--r--app/views/ci/variables/_table.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/_recaptcha_verification.html.haml15
-rw-r--r--app/views/layouts/_search.html.haml9
-rw-r--r--app/views/layouts/nav/projects_dropdown/_show.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.text.erb4
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml7
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml2
-rw-r--r--app/views/projects/blob/_header_content.html.haml3
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml10
-rw-r--r--app/views/projects/blob/viewers/_image.html.haml3
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/clusters/_cluster.html.haml7
-rw-r--r--app/views/projects/clusters/_empty_state.html.haml6
-rw-r--r--app/views/projects/clusters/_enabled.html.haml6
-rw-r--r--app/views/projects/clusters/_tabs.html.haml16
-rw-r--r--app/views/projects/clusters/index.html.haml14
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/diffs/_replaced_image_diff.html.haml16
-rw-r--r--app/views/projects/diffs/_single_image_diff.html.haml6
-rw-r--r--app/views/projects/environments/metrics.html.haml8
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml10
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml6
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml8
-rw-r--r--app/views/projects/project_members/_team.html.haml10
-rw-r--r--app/views/projects/project_members/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml8
-rw-r--r--app/views/projects/tree/_blob_item.html.haml3
-rw-r--r--app/views/shared/_label.html.haml33
-rw-r--r--app/views/shared/_outdated_browser.html.haml2
-rw-r--r--app/views/shared/_recaptcha_form.html.haml19
-rw-r--r--app/views/shared/_show_aside.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml21
-rw-r--r--app/views/shared/empty_states/_labels.html.haml10
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml17
-rw-r--r--app/views/shared/members/_member.html.haml24
-rw-r--r--app/views/shared/members/_requests.html.haml19
-rw-r--r--app/views/shared/notes/_note.html.haml4
-rw-r--r--app/workers/all_queues.yml98
-rw-r--r--app/workers/build_finished_worker.rb2
-rw-r--r--app/workers/build_hooks_worker.rb2
-rw-r--r--app/workers/build_queue_worker.rb2
-rw-r--r--app/workers/build_success_worker.rb2
-rw-r--r--app/workers/concerns/application_worker.rb24
-rw-r--r--app/workers/concerns/cluster_queue.rb2
-rw-r--r--app/workers/concerns/cronjob_queue.rb3
-rw-r--r--app/workers/concerns/gitlab/github_import/queue.rb4
-rw-r--r--app/workers/concerns/new_issuable.rb8
-rw-r--r--app/workers/concerns/pipeline_queue.rb10
-rw-r--r--app/workers/concerns/repository_check_queue.rb4
-rw-r--r--app/workers/create_pipeline_worker.rb2
-rw-r--r--app/workers/expire_job_cache_worker.rb2
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb2
-rw-r--r--app/workers/gitlab/github_import/advance_stage_worker.rb2
-rw-r--r--app/workers/pages_worker.rb2
-rw-r--r--app/workers/pipeline_hooks_worker.rb2
-rw-r--r--app/workers/pipeline_process_worker.rb2
-rw-r--r--app/workers/pipeline_success_worker.rb2
-rw-r--r--app/workers/pipeline_update_worker.rb2
-rw-r--r--app/workers/stage_update_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb7
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb16
320 files changed, 3897 insertions, 3212 deletions
diff --git a/app/assets/images/icon_image_comment.svg b/app/assets/images/icon_image_comment.svg
deleted file mode 100644
index cf6cb972940..00000000000
--- a/app/assets/images/icon_image_comment.svg
+++ /dev/null
@@ -1 +0,0 @@
-<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
deleted file mode 100644
index 83be91d3705..00000000000
--- a/app/assets/images/icon_image_comment@2x.svg
+++ /dev/null
@@ -1 +0,0 @@
-<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 4e967936ce0..68d6528758b 100644
--- a/app/assets/images/icons.json
+++ b/app/assets/images/icons.json
@@ -1 +1 @@
-{"iconCount":180,"spriteSize":82176,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","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","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} \ No newline at end of file
+{"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","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","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","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 77ce6b2d89f..fd8f7862911 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-double-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.536 7.95L1.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 1 1-1.414-1.415L5.536 7.95zm7 0L8.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.414-1.415l4.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-down" xmlns="http://www.w3.org/2000/svg"><path d="M10.472 7.282a.862.862 0 0 1 1.26-.006c.357.364.357.958 0 1.285L8.627 11.73A.886.886 0 0 1 8 12a.849.849 0 0 1-.627-.27L4.275 8.561a.904.904 0 0 1-.013-1.285.861.861 0 0 1 1.26-.007l2.486 2.527z"/></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="M4 12.5v-9A1.5 1.5 0 0 1 5.5 2h2.104c2.182 0 3.879.681 3.879 2.982 0 1.067-.517 2.227-1.374 2.595v.073C11.176 7.963 12 8.865 12 10.466 12 12.914 10.19 14 7.911 14H5.5A1.5 1.5 0 0 1 4 12.5zm2.376-5.696H7.49c1.164 0 1.665-.552 1.665-1.417 0-.94-.534-1.289-1.649-1.289h-1.13v2.706zm0 5.098h1.341c1.293 0 1.956-.515 1.956-1.62 0-1.049-.647-1.472-1.956-1.472H6.376v3.092z"/></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="bullhorn" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.143 10H7V4H3a3 3 0 1 0 0 6h.143l.734 5.141a1 1 0 0 0 .99.859h1.556a.5.5 0 0 0 .495-.57L6.143 10zM8 4c1.034.02 2.039-.274 3.014-.883.727-.455 1.836-1.334 3.328-2.637A1 1 0 0 1 16 1.233v10.764a1 1 0 0 1-1.595.803c-1.658-1.227-2.788-1.992-3.392-2.294-.781-.39-1.785-.559-3.013-.506V4z"/></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="chart" xmlns="http://www.w3.org/2000/svg"><path d="M15 14a1 1 0 0 1 0 2H2a2 2 0 0 1-2-2V1a1 1 0 1 1 2 0v13h13zM3.142 8.735l2.502-2.561a.5.5 0 0 1 .714-.003L8 7.833l3.592-4.553a.5.5 0 0 1 .796.015l2.516 3.454a.5.5 0 0 1 .096.295V12.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V9.085a.5.5 0 0 1 .142-.35z"/></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 9 13" id="collapse"><path d="M.084.25C.01.18-.015.12.008.071.031.024.093 0 .194 0h8.521c.1 0 .162.024.185.072.023.048-.002.107-.075.177l-4.11 3.935a.372.372 0 0 1-.11.072h-.301a.508.508 0 0 1-.11-.072L.084.249zM.377 6.88a.364.364 0 0 1-.26-.105.334.334 0 0 1-.11-.25v-.709c0-.096.036-.179.11-.249a.364.364 0 0 1 .26-.105h8.15c.101 0 .188.035.261.105.074.07.11.153.11.25v.709c0 .096-.036.179-.11.249a.364.364 0 0 1-.26.105H.377zM.084 12.132c-.074.07-.099.129-.076.177.023.048.085.072.186.072h8.521c.1 0 .162-.024.185-.072.023-.048-.002-.107-.075-.177l-4.11-3.935a.372.372 0 0 0-.11-.072h-.301a.508.508 0 0 0-.11.072l-4.11 3.935z"/></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="cut" xmlns="http://www.w3.org/2000/svg"><rect width="16" height="2" y="7" fill-rule="evenodd" rx="1"/></symbol><symbol viewBox="0 0 16 16" id="dashboard" xmlns="http://www.w3.org/2000/svg"><path d="M7.709 10.021l.696-2.6a.5.5 0 0 1 .966.26l-.657 2.45A2 2 0 0 1 10 12H6a2 2 0 0 1 1.709-1.979zM0 8.9a8 8 0 0 1 15.998 0H16v3.6a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5V8.9zM14 9A6 6 0 1 0 2 9v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9zM3.5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm9 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-7-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm5 0a.5.5 0 1 1 0-1 .5.5 0 0 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 105 26" id="double-headed-arrow" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.018 11.089L15.138.614c1.23-.911 3.086-.795 4.147.26.461.46.715 1.045.715 1.651v20.95C20 24.869 18.684 26 17.06 26a3.238 3.238 0 0 1-1.921-.614L1.019 14.911C-.212 14-.347 12.405.714 11.35c.094-.094.195-.18.303-.261zm102.964 0c.108.08.21.167.303.26 1.061 1.056.925 2.65-.303 3.562l-14.12 10.475A3.238 3.238 0 0 1 87.94 26C86.316 26 85 24.87 85 23.475V2.525c0-.606.254-1.192.715-1.65 1.061-1.056 2.917-1.172 4.146-.26l14.12 10.474zM35 17a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/></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 1600 1600" id="ellipsis_v" xmlns="http://www.w3.org/2000/svg"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></symbol><symbol viewBox="0 0 18 18" id="emoji_slightly_smiling_face" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445 2.91 2.91 0 0 0 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smile" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smiley" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z"/></symbol><symbol viewBox="0 0 16 16" id="epic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.985 8.044l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637A2 2 0 0 0 1.618 9h11.661a2 2 0 0 0 1.706-.956zm0 3l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637a2 2 0 0 0 .576.084h11.661a2 2 0 0 0 1.706-.956zM3.618 2h10.995a1 1 0 0 1 .948 1.316l-1.333 4a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l2-4A1 1 0 0 1 3.618 2zm-.382 4h9.322l.667-2H4.236l-1 2z"/></symbol><symbol viewBox="0 0 16 16" id="external-link" xmlns="http://www.w3.org/2000/svg"><path d="M13.121 4.177l-4.95 4.95a1 1 0 1 1-1.414-1.414l4.95-4.95-1.386-1.386a.5.5 0 0 1 .299-.85l4.709-.524a.5.5 0 0 1 .552.552l-.523 4.71a.5.5 0 0 1-.851.297l-1.386-1.385zM12 8.884a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-4z"/></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-addition" 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="hourglass" xmlns="http://www.w3.org/2000/svg"><path d="M10.331 4.889A2.988 2.988 0 0 0 11 3V2H5v1c0 .362.064.709.182 1.03l5.15.859zM3 14v-1c0-1.78.93-3.342 2.33-4.228.447-.327.67-.582.67-.764 0-.19-.242-.46-.725-.815A4.996 4.996 0 0 1 3 3V2H2a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2h-1v1a4.997 4.997 0 0 1-2.39 4.266c-.407.3-.61.545-.61.734 0 .19.203.434.61.734A4.997 4.997 0 0 1 13 13v1h1a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2h1zm8 0v-1a3 3 0 0 0-6 0v1h6z"/></symbol><symbol viewBox="0 0 24 30" id="image-comment-dark" xmlns="http://www.w3.org/2000/svg"><title>cursor_active</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="#FFF" 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="#1F78D1"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787a6.92 6.92 0 0 0-2.558.469c-.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-.068a.19.19 0 0 1 .033-.067.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-.26a2.57 2.57 0 0 0 .221-.342c.054-.103.114-.235.181-.395a4.18 4.18 0 0 0 .174-.51c-.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="#FFF" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 16 16" id="import" xmlns="http://www.w3.org/2000/svg"><path d="M9 8h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0L5.6 8.8A.5.5 0 0 1 6 8h1V1a1 1 0 1 1 2 0v7zM0 8a1 1 0 1 1 2 0 6 6 0 1 0 12 0 1 1 0 0 1 2 0A8 8 0 1 1 0 8z"/></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="italic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.5 12l2-8H6a1 1 0 1 1 0-2h6a1 1 0 0 1 0 2h-1.5l-2 8H10a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2h1.5z"/></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="menu" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.143 2h13.714C15.488 2 16 2.448 16 3s-.512 1-1.143 1H1.143C.512 4 0 3.552 0 3s.512-1 1.143-1zm0 5h13.714C15.488 7 16 7.448 16 8s-.512 1-1.143 1H1.143C.512 9 0 8.552 0 8s.512-1 1.143-1zm0 5h13.714c.631 0 1.143.448 1.143 1s-.512 1-1.143 1H1.143C.512 14 0 13.552 0 13s.512-1 1.143-1z"/></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="pencil-square" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V9zm.778-7.179l1.414 1.415-6.476 6.476a1 1 0 0 1-.498.27l-1.51.325.323-1.512a1 1 0 0 1 .27-.497l6.477-6.477zM15.607.407a1 1 0 0 1 0 1.414l-.708.707-1.414-1.414.707-.707a1 1 0 0 1 1.415 0z"/></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.114 6.958a4 4 0 0 0 5.283 4.775 1 1 0 1 1 .712 1.87A6 6 0 0 1 2.182 6.44l-.741-.2a.5.5 0 0 1-.12-.915l2.195-1.268a.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-.712-1.87 6 6 0 0 1 7.927 7.162l.742.2a.5.5 0 0 1 .12.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 12 16" id="scroll_down" xmlns="http://www.w3.org/2000/svg"><path class="evfirst-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043a.51.51 0 0 0 .321-.105c.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/><path class="evsecond-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/><path class="evthird-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91a.458.458 0 0 1-.136.09h-.37a.626.626 0 0 1-.136-.09"/></symbol><symbol viewBox="0 0 12 16" id="scroll_up" xmlns="http://www.w3.org/2000/svg"><path d="M1.048 1.845a.508.508 0 0 1-.32-.105c-.091-.07-.136-.154-.136-.25V.78c0-.095.045-.178.135-.249a.508.508 0 0 1 .321-.105h10.043a.51.51 0 0 1 .321.105c.09.07.136.154.136.25v.71c0 .095-.045.178-.136.249a.508.508 0 0 1-.32.105M.687 7.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 3.09A.458.458 0 0 0 6.257 3h-.37a.626.626 0 0 0-.136.09M.687 14.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 10.09a.458.458 0 0 0-.136-.09h-.37a.626.626 0 0 0-.136.09"/></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 14 14" id="spinner" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="7" cy="7" r="6" stroke="#000" stroke-opacity=".1" stroke-width="2"/><path fill="#000" fill-opacity=".1" fill-rule="nonzero" d="M7 0a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5V0z"/></g></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 14 14" id="status_canceled" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M5.2 3.8l4.9 4.9c.2.2.2.5 0 .7l-.7.7c-.2.2-.5.2-.7 0L3.8 5.2c-.2-.2-.2-.5 0-.7l.7-.7c.2-.2.5-.2.7 0"/></g></symbol><symbol viewBox="0 0 22 22" id="status_canceled_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M8.171 5.971l7.7 7.7a.76.76 0 0 1 0 1.1l-1.1 1.1a.76.76 0 0 1-1.1 0l-7.7-7.7a.76.76 0 0 1 0-1.1l1.1-1.1a.76.76 0 0 1 1.1 0"/></symbol><symbol viewBox="0 0 16 16" id="status_closed" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.83a1 1 0 0 1 1.414 1.416l-3.535 3.535a1 1 0 0 1-1.415.001l-2.12-2.12a1 1 0 1 1 1.413-1.415zM8 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 14 14" id="status_created" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><circle cx="7" cy="7" r="3.25"/></g></symbol><symbol viewBox="0 0 22 22" id="status_created_borderless" xmlns="http://www.w3.org/2000/svg"><circle cx="11" cy="11" r="5.107"/></symbol><symbol viewBox="0 0 14 14" id="status_failed" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 5.969L5.599 4.568a.29.29 0 0 0-.413.004l-.614.614a.294.294 0 0 0-.004.413L5.968 7l-1.4 1.401a.29.29 0 0 0 .004.413l.614.614c.113.114.3.117.413.004L7 8.032l1.401 1.4a.29.29 0 0 0 .413-.004l.614-.614a.294.294 0 0 0 .004-.413L8.032 7l1.4-1.401a.29.29 0 0 0-.004-.413l-.614-.614a.294.294 0 0 0-.413-.004L7 5.968z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_failed_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 9.38L8.798 7.178a.455.455 0 0 0-.65.006l-.964.965a.462.462 0 0 0-.006.65L9.38 11l-2.202 2.202a.455.455 0 0 0 .006.65l.965.964a.462.462 0 0 0 .65.006L11 12.62l2.202 2.202a.455.455 0 0 0 .65-.006l.964-.965a.462.462 0 0 0 .006-.65L12.62 11l2.202-2.202a.455.455 0 0 0-.006-.65l-.965-.964a.462.462 0 0 0-.65-.006L11 9.38z"/></symbol><symbol viewBox="0 0 14 14" id="status_manual" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_manual_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 11.99v-1.98l-1.238-.206c-.068-.273-.206-.546-.412-.956l.756-1.025-1.444-1.435-1.03.752a3.686 3.686 0 0 0-.963-.41L12.03 5.5h-1.994l-.206 1.23c-.343.068-.618.205-.962.41l-1.031-.752-1.444 1.435.687 1.025c-.206.341-.275.615-.412.956L5.5 9.941v1.981l1.237.205c.07.342.207.615.413.957l-.688 1.025 1.444 1.434 1.032-.683c.274.137.618.274.962.41l.206 1.23h2.063l.206-1.23c.344-.068.619-.205.963-.41l1.03.752 1.444-1.435-.756-1.025c.207-.341.344-.683.413-.956l1.031-.205zM11 13.017c-1.169 0-2.063-.889-2.063-2.05 0-1.162.894-2.05 2.063-2.05s2.063.888 2.063 2.05c0 1.161-.894 2.05-2.063 2.05z"/></symbol><symbol viewBox="0 0 22 22" id="status_notfound_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0v-1.43a5.9 5.9 0 0 0 .827-.492z"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></symbol><symbol viewBox="0 0 14 14" id="status_open" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M7 9.219a2.218 2.218 0 1 0 0-4.436A2.218 2.218 0 0 0 7 9.22zm0 1.12a3.338 3.338 0 1 1 0-6.676 3.338 3.338 0 0 1 0 6.676z"/></symbol><symbol viewBox="0 0 14 14" id="status_pending" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M4.7 5.3c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H5c-.2 0-.3-.1-.3-.3V5.3m3 0c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H8c-.2 0-.3-.1-.3-.3V5.3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_pending_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M7.386 8.329c0-.315.157-.472.471-.472h1.414c.315 0 .472.157.472.472v5.342c0 .315-.157.472-.472.472H7.857c-.314 0-.471-.157-.471-.472V8.33m4.714 0c0-.315.157-.472.471-.472h1.415c.314 0 .471.157.471.472v5.342c0 .315-.157.472-.471.472H12.57c-.314 0-.471-.157-.471-.472V8.33"/></symbol><symbol viewBox="0 0 14 14" id="status_running" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 3c2.2 0 4 1.8 4 4s-1.8 4-4 4c-1.3 0-2.5-.7-3.3-1.7L7 7V3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_running_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 4.714c3.457 0 6.286 2.829 6.286 6.286 0 3.457-2.829 6.286-6.286 6.286-2.043 0-3.929-1.1-5.186-2.672L11 11V4.714"/></symbol><symbol viewBox="0 0 14 14" id="status_skipped" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M6.415 7.04L4.579 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L5.341 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L6.415 7.04zm2.54 0L7.119 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L7.881 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L8.955 7.04z"/></symbol><symbol viewBox="0 0 22 22" id="status_skipped_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M14.072 11.063l-2.82 2.82a.46.46 0 0 0-.001.652l.495.495a.457.457 0 0 0 .653-.001l3.7-3.7a.46.46 0 0 0 .001-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.479-3.479a.464.464 0 0 0-.654.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/><path d="M10.08 11.063l-2.819 2.82a.46.46 0 0 0-.002.652l.496.495a.457.457 0 0 0 .652-.001l3.7-3.7a.46.46 0 0 0 .002-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.48-3.479a.464.464 0 0 0-.653.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/></symbol><symbol viewBox="0 0 14 14" id="status_success" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_success_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.866 12.095l-1.95-1.95a.462.462 0 0 0-.647.01l-.964.964a.46.46 0 0 0-.01.646l3.013 3.014a.787.787 0 0 0 1.106.008l.425-.425 4.854-4.853a.462.462 0 0 0 .002-.659l-.964-.964a.468.468 0 0 0-.658.002l-4.207 4.207z"/></symbol><symbol viewBox="0 0 14 14" id="status_success_solid" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7zm6.278.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 14 14" id="status_warning" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6 3.5c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v4c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-4m0 6c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v1c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-1"/></g></symbol><symbol viewBox="0 0 22 22" id="status_warning_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.429 5.5c0-.471.314-.786.785-.786h1.572c.471 0 .785.315.785.786v6.286c0 .471-.314.785-.785.785h-1.572c-.471 0-.785-.314-.785-.785V5.5m0 9.429c0-.472.314-.786.785-.786h1.572c.471 0 .785.314.785.786V16.5c0 .471-.314.786-.785.786h-1.572c-.471 0-.785-.315-.785-.786v-1.571"/></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="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="terminal" xmlns="http://www.w3.org/2000/svg"><path d="M7 8a.997.997 0 0 1-.293.707l-1.414 1.414a1 1 0 1 1-1.414-1.414L4.586 8l-.707-.707a1 1 0 1 1 1.414-1.414l1.414 1.414A.997.997 0 0 1 7 8zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4zm5 7h2a1 1 0 0 1 0 2H9a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-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="thumb-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="thumbtack" xmlns="http://www.w3.org/2000/svg"><path d="M7.125 9h-2.19a.5.5 0 0 1-.417-.777L6 6V2L5.362.724A.5.5 0 0 1 5.809 0h4.382a.5.5 0 0 1 .447.724L10 2v4l1.482 2.223a.5.5 0 0 1-.416.777H8.875L8 16l-.875-7z" fill-rule="evenodd"/></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.536 7.95L1.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 1 1-1.414-1.415L5.536 7.95zm7 0L8.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.414-1.415l4.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-down" xmlns="http://www.w3.org/2000/svg"><path d="M10.472 7.282a.862.862 0 0 1 1.26-.006c.357.364.357.958 0 1.285L8.627 11.73A.886.886 0 0 1 8 12a.849.849 0 0 1-.627-.27L4.275 8.561a.904.904 0 0 1-.013-1.285.861.861 0 0 1 1.26-.007l2.486 2.527z"/></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="M4 12.5v-9A1.5 1.5 0 0 1 5.5 2h2.104c2.182 0 3.879.681 3.879 2.982 0 1.067-.517 2.227-1.374 2.595v.073C11.176 7.963 12 8.865 12 10.466 12 12.914 10.19 14 7.911 14H5.5A1.5 1.5 0 0 1 4 12.5zm2.376-5.696H7.49c1.164 0 1.665-.552 1.665-1.417 0-.94-.534-1.289-1.649-1.289h-1.13v2.706zm0 5.098h1.341c1.293 0 1.956-.515 1.956-1.62 0-1.049-.647-1.472-1.956-1.472H6.376v3.092z"/></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="bullhorn" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.143 10H7V4H3a3 3 0 1 0 0 6h.143l.734 5.141a1 1 0 0 0 .99.859h1.556a.5.5 0 0 0 .495-.57L6.143 10zM8 4c1.034.02 2.039-.274 3.014-.883.727-.455 1.836-1.334 3.328-2.637A1 1 0 0 1 16 1.233v10.764a1 1 0 0 1-1.595.803c-1.658-1.227-2.788-1.992-3.392-2.294-.781-.39-1.785-.559-3.013-.506V4z"/></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="chart" xmlns="http://www.w3.org/2000/svg"><path d="M15 14a1 1 0 0 1 0 2H2a2 2 0 0 1-2-2V1a1 1 0 1 1 2 0v13h13zM3.142 8.735l2.502-2.561a.5.5 0 0 1 .714-.003L8 7.833l3.592-4.553a.5.5 0 0 1 .796.015l2.516 3.454a.5.5 0 0 1 .096.295V12.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V9.085a.5.5 0 0 1 .142-.35z"/></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 9 13" id="collapse"><path d="M.084.25C.01.18-.015.12.008.071.031.024.093 0 .194 0h8.521c.1 0 .162.024.185.072.023.048-.002.107-.075.177l-4.11 3.935a.372.372 0 0 1-.11.072h-.301a.508.508 0 0 1-.11-.072L.084.249zM.377 6.88a.364.364 0 0 1-.26-.105.334.334 0 0 1-.11-.25v-.709c0-.096.036-.179.11-.249a.364.364 0 0 1 .26-.105h8.15c.101 0 .188.035.261.105.074.07.11.153.11.25v.709c0 .096-.036.179-.11.249a.364.364 0 0 1-.26.105H.377zM.084 12.132c-.074.07-.099.129-.076.177.023.048.085.072.186.072h8.521c.1 0 .162-.024.185-.072.023-.048-.002-.107-.075-.177l-4.11-3.935a.372.372 0 0 0-.11-.072h-.301a.508.508 0 0 0-.11.072l-4.11 3.935z"/></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="cut" xmlns="http://www.w3.org/2000/svg"><rect width="16" height="2" y="7" fill-rule="evenodd" rx="1"/></symbol><symbol viewBox="0 0 16 16" id="dashboard" xmlns="http://www.w3.org/2000/svg"><path d="M7.709 10.021l.696-2.6a.5.5 0 0 1 .966.26l-.657 2.45A2 2 0 0 1 10 12H6a2 2 0 0 1 1.709-1.979zM0 8.9a8 8 0 0 1 15.998 0H16v3.6a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5V8.9zM14 9A6 6 0 1 0 2 9v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9zM3.5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm9 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-7-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm5 0a.5.5 0 1 1 0-1 .5.5 0 0 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 105 26" id="double-headed-arrow" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.018 11.089L15.138.614c1.23-.911 3.086-.795 4.147.26.461.46.715 1.045.715 1.651v20.95C20 24.869 18.684 26 17.06 26a3.238 3.238 0 0 1-1.921-.614L1.019 14.911C-.212 14-.347 12.405.714 11.35c.094-.094.195-.18.303-.261zm102.964 0c.108.08.21.167.303.26 1.061 1.056.925 2.65-.303 3.562l-14.12 10.475A3.238 3.238 0 0 1 87.94 26C86.316 26 85 24.87 85 23.475V2.525c0-.606.254-1.192.715-1.65 1.061-1.056 2.917-1.172 4.146-.26l14.12 10.474zM35 17a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/></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 1600 1600" id="ellipsis_v" xmlns="http://www.w3.org/2000/svg"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></symbol><symbol viewBox="0 0 18 18" id="emoji_slightly_smiling_face" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445 2.91 2.91 0 0 0 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smile" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smiley" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z"/></symbol><symbol viewBox="0 0 16 16" id="epic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.985 8.044l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637A2 2 0 0 0 1.618 9h11.661a2 2 0 0 0 1.706-.956zm0 3l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637a2 2 0 0 0 .576.084h11.661a2 2 0 0 0 1.706-.956zM3.618 2h10.995a1 1 0 0 1 .948 1.316l-1.333 4a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l2-4A1 1 0 0 1 3.618 2zm-.382 4h9.322l.667-2H4.236l-1 2z"/></symbol><symbol viewBox="0 0 16 16" id="external-link" xmlns="http://www.w3.org/2000/svg"><path d="M13.121 4.177l-4.95 4.95a1 1 0 1 1-1.414-1.414l4.95-4.95-1.386-1.386a.5.5 0 0 1 .299-.85l4.709-.524a.5.5 0 0 1 .552.552l-.523 4.71a.5.5 0 0 1-.851.297l-1.386-1.385zM12 8.884a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-4z"/></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-addition" 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="hourglass" xmlns="http://www.w3.org/2000/svg"><path d="M10.331 4.889A2.988 2.988 0 0 0 11 3V2H5v1c0 .362.064.709.182 1.03l5.15.859zM3 14v-1c0-1.78.93-3.342 2.33-4.228.447-.327.67-.582.67-.764 0-.19-.242-.46-.725-.815A4.996 4.996 0 0 1 3 3V2H2a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2h-1v1a4.997 4.997 0 0 1-2.39 4.266c-.407.3-.61.545-.61.734 0 .19.203.434.61.734A4.997 4.997 0 0 1 13 13v1h1a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2h1zm8 0v-1a3 3 0 0 0-6 0v1h6z"/></symbol><symbol viewBox="0 0 38 38" id="image-comment-dark" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#1F78D1"/><path fill="#FFF" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 38 38" id="image-comment-light" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 16 16" id="import" xmlns="http://www.w3.org/2000/svg"><path d="M9 8h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0L5.6 8.8A.5.5 0 0 1 6 8h1V1a1 1 0 1 1 2 0v7zM0 8a1 1 0 1 1 2 0 6 6 0 1 0 12 0 1 1 0 0 1 2 0A8 8 0 1 1 0 8z"/></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="italic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.5 12l2-8H6a1 1 0 1 1 0-2h6a1 1 0 0 1 0 2h-1.5l-2 8H10a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2h1.5z"/></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="menu" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.143 2h13.714C15.488 2 16 2.448 16 3s-.512 1-1.143 1H1.143C.512 4 0 3.552 0 3s.512-1 1.143-1zm0 5h13.714C15.488 7 16 7.448 16 8s-.512 1-1.143 1H1.143C.512 9 0 8.552 0 8s.512-1 1.143-1zm0 5h13.714c.631 0 1.143.448 1.143 1s-.512 1-1.143 1H1.143C.512 14 0 13.552 0 13s.512-1 1.143-1z"/></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="pencil-square" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V9zm.778-7.179l1.414 1.415-6.476 6.476a1 1 0 0 1-.498.27l-1.51.325.323-1.512a1 1 0 0 1 .27-.497l6.477-6.477zM15.607.407a1 1 0 0 1 0 1.414l-.708.707-1.414-1.414.707-.707a1 1 0 0 1 1.415 0z"/></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.114 6.958a4 4 0 0 0 5.283 4.775 1 1 0 1 1 .712 1.87A6 6 0 0 1 2.182 6.44l-.741-.2a.5.5 0 0 1-.12-.915l2.195-1.268a.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-.712-1.87 6 6 0 0 1 7.927 7.162l.742.2a.5.5 0 0 1 .12.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 12 16" id="scroll_down" xmlns="http://www.w3.org/2000/svg"><path class="ewfirst-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043a.51.51 0 0 0 .321-.105c.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/><path class="ewsecond-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/><path class="ewthird-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91a.458.458 0 0 1-.136.09h-.37a.626.626 0 0 1-.136-.09"/></symbol><symbol viewBox="0 0 12 16" id="scroll_up" xmlns="http://www.w3.org/2000/svg"><path d="M1.048 1.845a.508.508 0 0 1-.32-.105c-.091-.07-.136-.154-.136-.25V.78c0-.095.045-.178.135-.249a.508.508 0 0 1 .321-.105h10.043a.51.51 0 0 1 .321.105c.09.07.136.154.136.25v.71c0 .095-.045.178-.136.249a.508.508 0 0 1-.32.105M.687 7.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 3.09A.458.458 0 0 0 6.257 3h-.37a.626.626 0 0 0-.136.09M.687 14.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 10.09a.458.458 0 0 0-.136-.09h-.37a.626.626 0 0 0-.136.09"/></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 14 14" id="spinner" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="7" cy="7" r="6" stroke="#000" stroke-opacity=".1" stroke-width="2"/><path fill="#000" fill-opacity=".1" fill-rule="nonzero" d="M7 0a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5V0z"/></g></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 14 14" id="status_canceled" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M5.2 3.8l4.9 4.9c.2.2.2.5 0 .7l-.7.7c-.2.2-.5.2-.7 0L3.8 5.2c-.2-.2-.2-.5 0-.7l.7-.7c.2-.2.5-.2.7 0"/></g></symbol><symbol viewBox="0 0 22 22" id="status_canceled_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M8.171 5.971l7.7 7.7a.76.76 0 0 1 0 1.1l-1.1 1.1a.76.76 0 0 1-1.1 0l-7.7-7.7a.76.76 0 0 1 0-1.1l1.1-1.1a.76.76 0 0 1 1.1 0"/></symbol><symbol viewBox="0 0 16 16" id="status_closed" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.83a1 1 0 0 1 1.414 1.416l-3.535 3.535a1 1 0 0 1-1.415.001l-2.12-2.12a1 1 0 1 1 1.413-1.415zM8 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 14 14" id="status_created" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><circle cx="7" cy="7" r="3.25"/></g></symbol><symbol viewBox="0 0 22 22" id="status_created_borderless" xmlns="http://www.w3.org/2000/svg"><circle cx="11" cy="11" r="5.107"/></symbol><symbol viewBox="0 0 14 14" id="status_failed" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 5.969L5.599 4.568a.29.29 0 0 0-.413.004l-.614.614a.294.294 0 0 0-.004.413L5.968 7l-1.4 1.401a.29.29 0 0 0 .004.413l.614.614c.113.114.3.117.413.004L7 8.032l1.401 1.4a.29.29 0 0 0 .413-.004l.614-.614a.294.294 0 0 0 .004-.413L8.032 7l1.4-1.401a.29.29 0 0 0-.004-.413l-.614-.614a.294.294 0 0 0-.413-.004L7 5.968z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_failed_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 9.38L8.798 7.178a.455.455 0 0 0-.65.006l-.964.965a.462.462 0 0 0-.006.65L9.38 11l-2.202 2.202a.455.455 0 0 0 .006.65l.965.964a.462.462 0 0 0 .65.006L11 12.62l2.202 2.202a.455.455 0 0 0 .65-.006l.964-.965a.462.462 0 0 0 .006-.65L12.62 11l2.202-2.202a.455.455 0 0 0-.006-.65l-.965-.964a.462.462 0 0 0-.65-.006L11 9.38z"/></symbol><symbol viewBox="0 0 14 14" id="status_manual" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_manual_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 11.99v-1.98l-1.238-.206c-.068-.273-.206-.546-.412-.956l.756-1.025-1.444-1.435-1.03.752a3.686 3.686 0 0 0-.963-.41L12.03 5.5h-1.994l-.206 1.23c-.343.068-.618.205-.962.41l-1.031-.752-1.444 1.435.687 1.025c-.206.341-.275.615-.412.956L5.5 9.941v1.981l1.237.205c.07.342.207.615.413.957l-.688 1.025 1.444 1.434 1.032-.683c.274.137.618.274.962.41l.206 1.23h2.063l.206-1.23c.344-.068.619-.205.963-.41l1.03.752 1.444-1.435-.756-1.025c.207-.341.344-.683.413-.956l1.031-.205zM11 13.017c-1.169 0-2.063-.889-2.063-2.05 0-1.162.894-2.05 2.063-2.05s2.063.888 2.063 2.05c0 1.161-.894 2.05-2.063 2.05z"/></symbol><symbol viewBox="0 0 22 22" id="status_notfound_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0v-1.43a5.9 5.9 0 0 0 .827-.492z"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></symbol><symbol viewBox="0 0 14 14" id="status_open" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M7 9.219a2.218 2.218 0 1 0 0-4.436A2.218 2.218 0 0 0 7 9.22zm0 1.12a3.338 3.338 0 1 1 0-6.676 3.338 3.338 0 0 1 0 6.676z"/></symbol><symbol viewBox="0 0 14 14" id="status_pending" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M4.7 5.3c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H5c-.2 0-.3-.1-.3-.3V5.3m3 0c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H8c-.2 0-.3-.1-.3-.3V5.3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_pending_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M7.386 8.329c0-.315.157-.472.471-.472h1.414c.315 0 .472.157.472.472v5.342c0 .315-.157.472-.472.472H7.857c-.314 0-.471-.157-.471-.472V8.33m4.714 0c0-.315.157-.472.471-.472h1.415c.314 0 .471.157.471.472v5.342c0 .315-.157.472-.471.472H12.57c-.314 0-.471-.157-.471-.472V8.33"/></symbol><symbol viewBox="0 0 14 14" id="status_running" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 3c2.2 0 4 1.8 4 4s-1.8 4-4 4c-1.3 0-2.5-.7-3.3-1.7L7 7V3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_running_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 4.714c3.457 0 6.286 2.829 6.286 6.286 0 3.457-2.829 6.286-6.286 6.286-2.043 0-3.929-1.1-5.186-2.672L11 11V4.714"/></symbol><symbol viewBox="0 0 14 14" id="status_skipped" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M6.415 7.04L4.579 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L5.341 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L6.415 7.04zm2.54 0L7.119 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L7.881 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L8.955 7.04z"/></symbol><symbol viewBox="0 0 22 22" id="status_skipped_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M14.072 11.063l-2.82 2.82a.46.46 0 0 0-.001.652l.495.495a.457.457 0 0 0 .653-.001l3.7-3.7a.46.46 0 0 0 .001-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.479-3.479a.464.464 0 0 0-.654.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/><path d="M10.08 11.063l-2.819 2.82a.46.46 0 0 0-.002.652l.496.495a.457.457 0 0 0 .652-.001l3.7-3.7a.46.46 0 0 0 .002-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.48-3.479a.464.464 0 0 0-.653.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/></symbol><symbol viewBox="0 0 14 14" id="status_success" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_success_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.866 12.095l-1.95-1.95a.462.462 0 0 0-.647.01l-.964.964a.46.46 0 0 0-.01.646l3.013 3.014a.787.787 0 0 0 1.106.008l.425-.425 4.854-4.853a.462.462 0 0 0 .002-.659l-.964-.964a.468.468 0 0 0-.658.002l-4.207 4.207z"/></symbol><symbol viewBox="0 0 14 14" id="status_success_solid" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7zm6.278.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 14 14" id="status_warning" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6 3.5c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v4c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-4m0 6c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v1c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-1"/></g></symbol><symbol viewBox="0 0 22 22" id="status_warning_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.429 5.5c0-.471.314-.786.785-.786h1.572c.471 0 .785.315.785.786v6.286c0 .471-.314.785-.785.785h-1.572c-.471 0-.785-.314-.785-.785V5.5m0 9.429c0-.472.314-.786.785-.786h1.572c.471 0 .785.314.785.786V16.5c0 .471-.314.786-.785.786h-1.572c-.471 0-.785-.315-.785-.786v-1.571"/></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="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="terminal" xmlns="http://www.w3.org/2000/svg"><path d="M7 8a.997.997 0 0 1-.293.707l-1.414 1.414a1 1 0 1 1-1.414-1.414L4.586 8l-.707-.707a1 1 0 1 1 1.414-1.414l1.414 1.414A.997.997 0 0 1 7 8zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4zm5 7h2a1 1 0 0 1 0 2H9a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-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="thumb-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="thumbtack" xmlns="http://www.w3.org/2000/svg"><path d="M7.125 9h-2.19a.5.5 0 0 1-.417-.777L6 6V2L5.362.724A.5.5 0 0 1 5.809 0h4.382a.5.5 0 0 1 .447.724L10 2v4l1.482 2.223a.5.5 0 0 1-.416.777H8.875L8 16l-.875-7z" fill-rule="evenodd"/></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/illustrations/image_comment_light_cursor.svg b/app/assets/images/illustrations/image_comment_light_cursor.svg
new file mode 100644
index 00000000000..ac712ea0c96
--- /dev/null
+++ b/app/assets/images/illustrations/image_comment_light_cursor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/image_comment_light_cursor@2x.svg b/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
new file mode 100644
index 00000000000..02943acd9d7
--- /dev/null
+++ b/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 5d060165f4b..6a0662ba903 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -1,9 +1,10 @@
/* eslint-disable no-param-reassign, class-methods-use-this */
-/* global Pager */
import Cookies from 'js-cookie';
+import Pager from './pager';
+import { localTimeAgo } from './lib/utils/datetime_utility';
-class Activities {
+export default class Activities {
constructor() {
Pager.init(20, true, false, data => data, this.updateTooltips);
@@ -15,7 +16,7 @@ class Activities {
}
updateTooltips() {
- gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
+ localTimeAgo($('.js-timeago', '.content_list'));
}
reloadActivities() {
@@ -33,6 +34,3 @@ class Activities {
$sender.closest('li').toggleClass('active');
}
}
-
-window.gl = window.gl || {};
-window.gl.Activities = Activities;
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 34669dd13d6..c1f7fa2aced 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,62 +1,59 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
-
-window.Admin = (function() {
- function Admin() {
- var modal, showBlacklistType;
- $('input#user_force_random_password').on('change', function(elem) {
- var elems;
- elems = $('#user_password, #user_password_confirmation');
- if ($(this).attr('checked')) {
- return elems.val('').attr('disabled', true);
- } else {
- return elems.removeAttr('disabled');
- }
- });
- $('body').on('click', '.js-toggle-colors-link', function(e) {
- e.preventDefault();
- return $('.js-toggle-colors-container').toggle();
- });
- $('.log-tabs a').click(function(e) {
- e.preventDefault();
- return $(this).tab('show');
- });
- $('.log-bottom').click(function(e) {
- var visible_log;
- e.preventDefault();
- visible_log = $(".file-content:visible");
- return visible_log.animate({
- scrollTop: visible_log.find('ol').height()
- }, "fast");
- });
- modal = $('.change-owner-holder');
- $('.change-owner-link').bind("click", function(e) {
- e.preventDefault();
- $(this).hide();
- return modal.show();
- });
- $('.change-owner-cancel-link').bind("click", function(e) {
- e.preventDefault();
- modal.hide();
- return $('.change-owner-link').show();
- });
- $('li.project_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
- });
- $('li.group_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
- });
- showBlacklistType = function() {
- if ($("input[name='blacklist_type']:checked").val() === 'file') {
- $('.blacklist-file').show();
- return $('.blacklist-raw').hide();
- } else {
- $('.blacklist-file').hide();
- return $('.blacklist-raw').show();
- }
- };
- $("input[name='blacklist_type']").click(showBlacklistType);
- showBlacklistType();
+import { refreshCurrentPage } from './lib/utils/url_utility';
+
+function showBlacklistType() {
+ if ($('input[name="blacklist_type"]:checked').val() === 'file') {
+ $('.blacklist-file').show();
+ $('.blacklist-raw').hide();
+ } else {
+ $('.blacklist-file').hide();
+ $('.blacklist-raw').show();
}
+}
+
+export default function adminInit() {
+ const modal = $('.change-owner-holder');
+
+ $('input#user_force_random_password').on('change', function randomPasswordClick() {
+ const $elems = $('#user_password, #user_password_confirmation');
+ if ($(this).attr('checked')) {
+ $elems.val('').attr('disabled', true);
+ } else {
+ $elems.removeAttr('disabled');
+ }
+ });
+
+ $('body').on('click', '.js-toggle-colors-link', (e) => {
+ e.preventDefault();
+ $('.js-toggle-colors-container').toggle();
+ });
+
+ $('.log-tabs a').on('click', function logTabsClick(e) {
+ e.preventDefault();
+ $(this).tab('show');
+ });
+
+ $('.log-bottom').on('click', (e) => {
+ e.preventDefault();
+ const $visibleLog = $('.file-content:visible');
+ $visibleLog.animate({
+ scrollTop: $visibleLog.find('ol').height(),
+ }, 'fast');
+ });
+
+ $('.change-owner-link').on('click', function changeOwnerLinkClick(e) {
+ e.preventDefault();
+ $(this).hide();
+ modal.show();
+ });
+
+ $('.change-owner-cancel-link').on('click', (e) => {
+ e.preventDefault();
+ modal.hide();
+ $('.change-owner-link').show();
+ });
+
+ $('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage);
- return Admin;
-})();
+ $("input[name='blacklist_type']").on('click', showBlacklistType);
+ showBlacklistType();
+}
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
deleted file mode 100644
index 88756884d16..00000000000
--- a/app/assets/javascripts/aside.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
-
-window.Aside = (function() {
- function Aside() {
- $(document).off("click", "a.show-aside");
- $(document).on("click", 'a.show-aside', function(e) {
- var btn, icon;
- e.preventDefault();
- btn = $(e.currentTarget);
- icon = btn.find('i');
- if (icon.hasClass('fa-angle-left')) {
- btn.parent().find('section').hide();
- btn.parent().find('aside').fadeIn();
- return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
- } else {
- btn.parent().find('aside').hide();
- btn.parent().find('section').fadeIn();
- return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
- }
- });
- }
-
- return Aside;
-})();
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
new file mode 100644
index 00000000000..1cf0b960eb0
--- /dev/null
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -0,0 +1,42 @@
+import { n__ } from '../locale';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+
+export default class SecretValues {
+ constructor(container) {
+ this.container = container;
+ }
+
+ init() {
+ this.values = this.container.querySelectorAll('.js-secret-value');
+ this.placeholders = this.container.querySelectorAll('.js-secret-value-placeholder');
+ this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
+
+ this.revealText = n__('Reveal value', 'Reveal values', this.values.length);
+ this.hideText = n__('Hide value', 'Hide values', this.values.length);
+
+ const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
+ this.updateDom(isRevealed);
+
+ this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ }
+
+ onRevealButtonClicked() {
+ const previousIsRevealed = convertPermissionToBoolean(
+ this.revealButton.dataset.secretRevealStatus,
+ );
+ this.updateDom(!previousIsRevealed);
+ }
+
+ updateDom(isRevealed) {
+ this.values.forEach((value) => {
+ value.classList.toggle('hide', !isRevealed);
+ });
+
+ this.placeholders.forEach((placeholder) => {
+ placeholder.classList.toggle('hide', isRevealed);
+ });
+
+ this.revealButton.textContent = isRevealed ? this.hideText : this.revealText;
+ this.revealButton.dataset.secretRevealStatus = isRevealed;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index b70b0a9bbf8..417ac31fc86 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -5,6 +5,7 @@
// %button.js-toggle-button
// %div.js-toggle-content
//
+import { getLocationHash } from '../lib/utils/url_utility';
$(() => {
function toggleContainer(container, toggleState) {
@@ -32,7 +33,7 @@ $(() => {
// If we're accessing a permalink, ensure it is not inside a
// closed js-toggle-container!
- const hash = window.gl.utils.getLocationHash();
+ const hash = getLocationHash();
const anchor = hash && document.getElementById(hash);
const container = anchor && $(anchor).closest('.js-toggle-container');
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 0d590a9dbc4..f7ae6f1cd12 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
import Dropzone from 'dropzone';
-import '../lib/utils/url_utility';
+import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
@@ -49,7 +49,7 @@ export default class BlobFileDropzone {
});
this.on('success', function (header, response) {
$('#modal-upload-blob').modal('hide');
- window.gl.utils.visitUrl(response.filePath);
+ visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
dropzoneMessage.addClass(HIDDEN_CLASS);
diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js
index c8f68860fbd..d36d9f0de2d 100644
--- a/app/assets/javascripts/blob/blob_line_permalink_updater.js
+++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js
@@ -1,7 +1,9 @@
+import { getLocationHash } from '../lib/utils/url_utility';
+
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index faa76da964f..616de2347e1 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -1,9 +1,9 @@
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
/* global MilestoneSelect */
-/* global Sidebar */
import Vue from 'vue';
import Flash from '../../flash';
+import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import assignees from '../../sidebar/components/assignees/assignees';
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 50d0cb5c86d..5662802525e 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -121,7 +121,7 @@ export default class ImageFile {
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
- ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
@@ -158,7 +158,7 @@ export default class ImageFile {
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
- ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 9b952ea7b60..3a03cbf6b90 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,9 +1,10 @@
/* eslint-disable func-names, wrap-iife, consistent-return,
no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
prefer-template, object-shorthand, prefer-arrow-callback */
-/* global Pager */
import { pluralize } from './lib/utils/text_utility';
+import { localTimeAgo } from './lib/utils/datetime_utility';
+import Pager from './pager';
export default (function () {
const CommitsList = {};
@@ -91,7 +92,7 @@ export default (function () {
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
}
- gl.utils.localTimeAgo($processedData.find('.js-timeago'));
+ localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 9e5dbd64a7e..144caf1d278 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,7 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
+import { localTimeAgo } from './lib/utils/datetime_utility';
-window.Compare = (function() {
- function Compare(opts) {
+export default class Compare {
+ constructor(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading");
@@ -34,12 +35,12 @@ window.Compare = (function() {
this.initialState();
}
- Compare.prototype.initialState = function() {
+ initialState() {
this.getSourceHtml();
- return this.getTargetHtml();
- };
+ this.getTargetHtml();
+ }
- Compare.prototype.getTargetProject = function() {
+ getTargetProject() {
return $.ajax({
url: this.opts.targetProjectUrl,
data: {
@@ -52,22 +53,22 @@ window.Compare = (function() {
return $('.js-target-branch-dropdown .dropdown-content').html(html);
}
});
- };
+ }
- Compare.prototype.getSourceHtml = function() {
- return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
+ getSourceHtml() {
+ return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val()
});
- };
+ }
- Compare.prototype.getTargetHtml = function() {
- return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
+ getTargetHtml() {
+ return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val()
});
- };
+ }
- Compare.prototype.sendAjax = function(url, loading, target, data) {
+ static sendAjax(url, loading, target, data) {
var $target;
$target = $(target);
return $.ajax({
@@ -81,10 +82,8 @@ window.Compare = (function() {
loading.hide();
$target.html(html);
var className = '.' + $target[0].className.replace(' ', '.');
- gl.utils.localTimeAgo($('.js-timeago', className));
+ localTimeAgo($('.js-timeago', className));
}
});
- };
-
- return Compare;
-})();
+ }
+}
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 72c0d98d47c..e633ef8a29e 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,68 +1,60 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
-window.CompareAutocomplete = (function() {
- function CompareAutocomplete() {
- this.initDropdown();
- }
-
- CompareAutocomplete.prototype.initDropdown = function() {
- return $('.js-compare-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
- const $dropdownContainer = $dropdown.closest('.dropdown');
- const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
- const $filterInput = $('input[type="search"]', $dropdownContainer);
- $dropdown.glDropdown({
- data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
- ref: $dropdown.data('ref'),
- search: term,
- }
- }).done(function(refs) {
- return callback(refs);
- });
- },
- selectable: true,
- filterable: true,
- filterRemote: true,
- fieldName: $dropdown.data('field-name'),
- filterInput: 'input[type="search"]',
- renderRow: function(ref) {
- var link;
- if (ref.header != null) {
- return $('<li />').addClass('dropdown-header').text(ref.header);
- } else {
- link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
- return $('<li />').append(link);
+export default function initCompareAutocomplete() {
+ $('.js-compare-dropdown').each(function() {
+ var $dropdown, selected;
+ $dropdown = $(this);
+ selected = $dropdown.data('selected');
+ const $dropdownContainer = $dropdown.closest('.dropdown');
+ const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
+ const $filterInput = $('input[type="search"]', $dropdownContainer);
+ $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: $dropdown.data('refs-url'),
+ data: {
+ ref: $dropdown.data('ref'),
+ search: term,
}
- },
- id: function(obj, $el) {
- return $el.attr('data-ref');
- },
- toggleLabel: function(obj, $el) {
- return $el.text().trim();
- }
- });
- $filterInput.on('keyup', (e) => {
- const keyCode = e.keyCode || e.which;
- if (keyCode !== 13) return;
- const text = $filterInput.val();
- $fieldInput.val(text);
- $('.dropdown-toggle-text', $dropdown).text(text);
- $dropdownContainer.removeClass('open');
- });
-
- $dropdownContainer.on('click', '.dropdown-content a', (e) => {
- $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
- if ($dropdown.hasClass('has-tooltip')) {
- $dropdown.tooltip('fixTitle');
+ }).done(function(refs) {
+ return callback(refs);
+ });
+ },
+ selectable: true,
+ filterable: true,
+ filterRemote: true,
+ fieldName: $dropdown.data('field-name'),
+ filterInput: 'input[type="search"]',
+ renderRow: function(ref) {
+ var link;
+ if (ref.header != null) {
+ return $('<li />').addClass('dropdown-header').text(ref.header);
+ } else {
+ link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+ return $('<li />').append(link);
}
- });
+ },
+ id: function(obj, $el) {
+ return $el.attr('data-ref');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ }
+ });
+ $filterInput.on('keyup', (e) => {
+ const keyCode = e.keyCode || e.which;
+ if (keyCode !== 13) return;
+ const text = $filterInput.val();
+ $fieldInput.val(text);
+ $('.dropdown-toggle-text', $dropdown).text(text);
+ $dropdownContainer.removeClass('open');
});
- };
- return CompareAutocomplete;
-})();
+ $dropdownContainer.on('click', '.dropdown-content a', (e) => {
+ $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
+ if ($dropdown.hasClass('has-tooltip')) {
+ $dropdown.tooltip('fixTitle');
+ }
+ });
+ });
+}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 46b68ebe158..74520675a7c 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -9,7 +9,7 @@ export default class ContextualSidebar {
}
initDomElements() {
- this.$page = $('.page-with-sidebar');
+ this.$page = $('.layout-page');
this.$sidebar = $('.nav-sidebar');
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay');
@@ -28,7 +28,7 @@ export default class ContextualSidebar {
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => {
- const value = !this.$sidebar.hasClass('sidebar-icons-only');
+ const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
this.toggleCollapsedSidebar(value);
});
@@ -43,16 +43,16 @@ export default class ContextualSidebar {
}
toggleSidebarNav(show) {
- this.$sidebar.toggleClass('nav-sidebar-expanded', show);
+ this.$sidebar.toggleClass('sidebar-expanded-mobile', show);
this.$overlay.toggleClass('mobile-nav-open', show);
- this.$sidebar.removeClass('sidebar-icons-only');
+ this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
toggleCollapsedSidebar(collapsed) {
const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) {
- this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
+ this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
ContextualSidebar.setCollapsedCookie(collapsed);
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index b41d464475f..2a05c6f001e 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -1,5 +1,6 @@
<script>
import actionBtn from './action_btn.vue';
+ import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
props: {
@@ -21,7 +22,7 @@
},
computed: {
timeagoDate() {
- return gl.utils.getTimeago().format(this.deployKey.created_at);
+ return getTimeago().format(this.deployKey.created_at);
},
editDeployKeyPath() {
return `${this.endpoint}/${this.deployKey.id}/edit`;
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index c8874e48c09..a162424b3cf 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,4 +1,4 @@
-import './lib/utils/url_utility';
+import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index';
@@ -31,7 +31,7 @@ export default class Diff {
isBound = true;
}
- if (gl.utils.getLocationHash()) {
+ if (getLocationHash()) {
this.highlightSelectedLine();
}
@@ -73,7 +73,7 @@ export default class Diff {
}
openAnchoredDiff(cb) {
- const locationHash = gl.utils.getLocationHash();
+ const locationHash = getLocationHash();
const anchoredDiff = locationHash && locationHash.split('_')[0];
if (!anchoredDiff) return;
@@ -128,7 +128,7 @@ export default class Diff {
}
// eslint-disable-next-line class-methods-use-this
highlightSelectedLine() {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index dc43e4b2cc7..1b8a9af9390 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -2,6 +2,7 @@
/* global NoteModel */
import Vue from 'vue';
+import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
constructor (discussionId) {
@@ -71,7 +72,7 @@ class DiscussionModel {
$(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
}
- gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
+ localTimeAgo($('.js-timeago', `${discussionSelector}`));
} else {
$discussionHeadline.remove();
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 678af8f7b7a..62867c56214 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -7,34 +7,35 @@ import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select';
/* global MilestoneSelect */
import NewBranchForm from './new_branch_form';
-/* global NotificationsForm */
-/* global NotificationsDropdown */
+import NotificationsForm from './notifications_form';
+import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
-/* global LineHighlighter */
+import LineHighlighter from './line_highlighter';
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
-/* global Search */
-/* global Admin */
+import Search from './search';
+import initAdmin from './admin';
import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
-/* global MergeRequest */
-/* global Compare */
-/* global CompareAutocomplete */
-/* global ProjectFindFile */
+import MergeRequest from './merge_request';
+import Compare from './compare';
+import initCompareAutocomplete from './compare_autocomplete';
+import ProjectFindFile from './project_find_file';
import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
-/* global Sidebar */
+import Sidebar from './right_sidebar';
import IssuableTemplateSelectors from './templates/issuable_template_selectors';
import Flash from './flash';
import CommitsList from './commits';
import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
+import SecretValues from './behaviors/secret_values';
import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
import GroupsList from './groups_list';
@@ -90,7 +91,8 @@ import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
-import ProjectVariables from './project_variables';
+import SearchAutocomplete from './search_autocomplete';
+import Activities from './activities';
(function() {
var Dispatcher;
@@ -333,7 +335,7 @@ import ProjectVariables from './project_variables';
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
- new gl.Activities();
+ new Activities();
break;
case 'projects:commit:show':
new Diff();
@@ -354,7 +356,7 @@ import ProjectVariables from './project_variables';
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:activity':
- new gl.Activities();
+ new Activities();
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:commits:show':
@@ -372,7 +374,7 @@ import ProjectVariables from './project_variables';
if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer();
- if ($('.project-show-activity').length) new gl.Activities();
+ if ($('.project-show-activity').length) new Activities();
$('#tree-slider').waitForImages(function() {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
@@ -406,13 +408,13 @@ import ProjectVariables from './project_variables';
});
break;
case 'groups:activity':
- new gl.Activities();
+ new Activities();
break;
case 'groups:show':
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
shortcut_handler = new ShortcutsNavigation();
new NotificationsForm();
- new NotificationsDropdown();
+ notificationsDropdown();
new ProjectsList();
if (newGroupChildWrapper) {
@@ -525,8 +527,18 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
+
+ const runnerToken = document.querySelector('.js-secret-runner-token');
+ if (runnerToken) {
+ const runnerTokenSecretValue = new SecretValues(runnerToken);
+ runnerTokenSecretValue.init();
+ }
case 'groups:settings:ci_cd:show':
- new ProjectVariables();
+ const secretVariableTable = document.querySelector('.js-secret-variable-table');
+ if (secretVariableTable) {
+ const secretVariableTableValues = new SecretValues(secretVariableTable);
+ secretVariableTableValues.init();
+ }
break;
case 'ci:lints:create':
case 'ci:lints:show':
@@ -583,7 +595,7 @@ import ProjectVariables from './project_variables';
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin':
- new Admin();
+ initAdmin();
switch (path[1]) {
case 'broadcast_messages':
initBroadcastMessagesForm();
@@ -615,14 +627,14 @@ import ProjectVariables from './project_variables';
break;
case 'profiles':
new NotificationsForm();
- new NotificationsDropdown();
+ notificationsDropdown();
break;
case 'projects':
new Project();
projectAvatar();
switch (path[1]) {
case 'compare':
- new CompareAutocomplete();
+ initCompareAutocomplete();
break;
case 'edit':
shortcut_handler = new ShortcutsNavigation();
@@ -638,7 +650,7 @@ import ProjectVariables from './project_variables';
case 'show':
new Star();
new ProjectNew();
- new NotificationsDropdown();
+ notificationsDropdown();
break;
case 'wikis':
new Wikis();
@@ -683,7 +695,7 @@ import ProjectVariables from './project_variables';
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
- return new gl.SearchAutocomplete();
+ return new SearchAutocomplete();
}
};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 69c57f923b6..2ba85c7da97 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
@@ -566,7 +567,7 @@ class FilteredSearchManager {
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
- gl.utils.visitUrl(parameterizedUrl);
+ visitUrl(parameterizedUrl);
}
}
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 98837c3b2a0..6110d961609 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -21,7 +21,7 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
-export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only');
+export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
export const canShowActiveSubItems = (el) => {
if (el.classList.contains('active') && !isSidebarCollapsed()) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 7ca783d3af6..cf4a70e321e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -2,6 +2,7 @@
/* global fuzzaldrinPlus */
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
@@ -852,7 +853,7 @@ GitLabDropdown = (function() {
if ($el.length) {
var href = $el.attr('href');
if (href && href !== '#') {
- gl.utils.visitUrl(href);
+ visitUrl(href);
} else {
$el.trigger('click');
}
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index e7232ca3712..743c049e9fb 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -1,13 +1,14 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
import _ from 'underscore';
-import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
-import { n__ } from '../locale';
+import { n__, s__, createDateTimeFormat, sprintf } from '../locale';
export default (function() {
- function ContributorsStatGraph() {}
+ function ContributorsStatGraph() {
+ this.dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
+ }
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
@@ -95,11 +96,15 @@ export default (function() {
};
ContributorsStatGraph.prototype.change_date_header = function() {
- var print, print_date_format, x_domain;
- x_domain = ContributorsGraph.prototype.x_domain;
- print_date_format = d3.time.format("%B %e %Y");
- print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
- return $("#date_header").text(print);
+ const x_domain = ContributorsGraph.prototype.x_domain;
+ const formattedDateRange = sprintf(
+ s__('ContributorsPage|%{startDate} – %{endDate}'),
+ {
+ startDate: this.dateFormat.format(new Date(x_domain[0])),
+ endDate: this.dateFormat.format(new Date(x_domain[1])),
+ },
+ );
+ return $('#date_header').text(formattedDateRange);
};
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index f64b4638485..187f3c008e8 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
import _ from 'underscore';
import d3 from 'd3';
+import { dateTickFormat } from '../lib/utils/tick_formats';
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
const hasProp = {}.hasOwnProperty;
@@ -93,9 +94,12 @@ export const ContributorsMasterGraph = (function(superClass) {
extend(ContributorsMasterGraph, superClass);
function ContributorsMasterGraph(data1) {
+ const $parentElement = $('#contributors-master');
+ const parentPadding = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
+
this.data = data1;
this.update_content = this.update_content.bind(this);
- this.width = $('.content').width() - 70;
+ this.width = $('.content').width() - parentPadding - (this.MARGIN.left + this.MARGIN.right);
this.height = 200;
this.x = null;
this.y = null;
@@ -131,7 +135,10 @@ export const ContributorsMasterGraph = (function(superClass) {
};
ContributorsMasterGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
+ this.x_axis = d3.svg.axis()
+ .scale(this.x)
+ .orient('bottom')
+ .tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};
@@ -219,7 +226,11 @@ export const ContributorsAuthorGraph = (function(superClass) {
};
ContributorsAuthorGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
+ this.x_axis = d3.svg.axis()
+ .scale(this.x)
+ .orient('bottom')
+ .ticks(8)
+ .tickFormat(dateTickFormat);
return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
};
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 2c0b6ab4ea8..241e026b84c 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -5,7 +5,7 @@ import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants';
-
+import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue';
export default {
@@ -93,7 +93,7 @@ export default {
this.isLoading = false;
$.scrollTo(0);
- const currentPath = gl.utils.mergeUrlParams({ page }, window.location.href);
+ const currentPath = mergeUrlParams({ page }, window.location.href);
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index c76ce762b54..6421547bbde 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -1,4 +1,5 @@
<script>
+import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub';
@@ -60,7 +61,7 @@ export default {
if (this.hasChildren) {
eventHub.$emit('toggleChildren', this.group);
} else {
- gl.utils.visitUrl(this.group.relativePath);
+ visitUrl(this.group.relativePath);
}
}
},
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 09cb79c1afd..58ba5aff7cf 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -1,7 +1,7 @@
<script>
import { s__ } from '../../locale';
import tooltip from '../../vue_shared/directives/tooltip';
-import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
@@ -9,7 +9,7 @@ import Icon from '../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
- PopupDialog,
+ modal,
},
directives: {
tooltip,
@@ -27,7 +27,7 @@ export default {
},
data() {
return {
- dialogStatus: false,
+ modalStatus: false,
};
},
computed: {
@@ -43,10 +43,10 @@ export default {
},
methods: {
onLeaveGroup() {
- this.dialogStatus = true;
+ this.modalStatus = true;
},
leaveGroup(leaveConfirmed) {
- this.dialogStatus = false;
+ this.modalStatus = false;
if (leaveConfirmed) {
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
@@ -82,8 +82,8 @@ export default {
class="fa fa-sign-out"
aria-hidden="true"/>
</a>
- <popup-dialog
- v-show="dialogStatus"
+ <modal
+ v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js
index 8e273579aae..a120d501e35 100644
--- a/app/assets/javascripts/groups/new_group_child.js
+++ b/app/assets/javascripts/groups/new_group_child.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import DropLab from '../droplab/drop_lab';
import ISetter from '../droplab/plugins/input_setter';
@@ -54,9 +55,9 @@ export default class NewGroupChild {
onClickNewGroupChildButton(e) {
if (e.target.dataset.action === NEW_PROJECT) {
- gl.utils.visitUrl(this.newGroupPath);
+ visitUrl(this.newGroupPath);
} else if (e.target.dataset.action === NEW_SUBGROUP) {
- gl.utils.visitUrl(this.subgroupPath);
+ visitUrl(this.subgroupPath);
}
}
}
diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js
index 6a6a668308d..eddaeda9578 100644
--- a/app/assets/javascripts/image_diff/helpers/badge_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js
@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
}
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');
+ const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']);
+ buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
- buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl);
}
diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js
index ada693afc46..5d4c1851fe5 100644
--- a/app/assets/javascripts/init_issuable_sidebar.js
+++ b/app/assets/javascripts/init_issuable_sidebar.js
@@ -2,7 +2,7 @@
/* global MilestoneSelect */
import LabelsSelect from './labels_select';
import IssuableContext from './issuable_context';
-/* global Sidebar */
+import Sidebar from './right_sidebar';
import DueDateSelectors from './due_date_select';
@@ -15,5 +15,5 @@ export default () => {
new LabelsSelect();
new IssuableContext(sidebarOptions.currentUser);
new DueDateSelectors();
- window.sidebar = new Sidebar();
+ Sidebar.initialize();
};
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index ba2b6737988..bf77b93b643 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -21,7 +21,7 @@ export default class IssuableBulkUpdateSidebar {
}
initDomElements() {
- this.$page = $('.page-with-sidebar');
+ this.$page = $('.layout-page');
this.$sidebar = $('.right-sidebar');
this.$sidebarInnerContainer = this.$sidebar.find('.issuable-sidebar');
this.$bulkEditCancelBtn = $('.js-bulk-update-menu-hide');
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 7de07e9403d..411c820cc43 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper';
export default class Issue {
constructor() {
- if ($('a.btn-close').length) {
- this.taskList = new TaskList({
- dataType: 'issue',
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: (result) => {
- document.querySelector('#task_status').innerText = result.task_status;
- document.querySelector('#task_status_short').innerText = result.task_status_short;
- }
- });
- this.initIssueBtnEventListeners();
- }
+ if ($('a.btn-close').length) this.initIssueBtnEventListeners();
Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
@@ -59,7 +48,7 @@ export default class Issue {
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
- const isClosedBadge = $('div.status-box-closed');
+ const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 5bdc7c99503..25ebe5314e0 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,5 +1,6 @@
<script>
import Visibility from 'visibilityjs';
+import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import Service from '../services/index';
@@ -8,7 +9,7 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
-import '../../lib/utils/url_utility';
+import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
props: {
@@ -149,6 +150,11 @@ export default {
editedComponent,
formComponent,
},
+
+ mixins: [
+ recaptchaModalImplementor,
+ ],
+
methods: {
openForm() {
if (!this.showForm) {
@@ -164,12 +170,14 @@ export default {
closeForm() {
this.showForm = false;
},
+
updateIssuable() {
this.service.updateIssuable(this.store.formState)
.then(res => res.json())
+ .then(data => this.checkForSpam(data))
.then((data) => {
if (location.pathname !== data.web_url) {
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
}
return this.service.getData();
@@ -179,11 +187,24 @@ export default {
this.store.updateState(data);
eventHub.$emit('close.form');
})
- .catch(() => {
- eventHub.$emit('close.form');
- window.Flash(`Error updating ${this.issuableType}`);
+ .catch((error) => {
+ if (error && error.name === 'SpamError') {
+ this.openRecaptcha();
+ } else {
+ eventHub.$emit('close.form');
+ window.Flash(`Error updating ${this.issuableType}`);
+ }
});
},
+
+ closeRecaptchaModal() {
+ this.store.setFormState({
+ updateLoading: false,
+ });
+
+ this.closeRecaptcha();
+ },
+
deleteIssuable() {
this.service.deleteIssuable()
.then(res => res.json())
@@ -191,7 +212,7 @@ export default {
// Stop the poll so we don't get 404's with the issuable not existing
this.poll.stop();
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
})
.catch(() => {
eventHub.$emit('close.form');
@@ -237,9 +258,9 @@ export default {
</script>
<template>
- <div>
+<div>
+ <div v-if="canUpdate && showForm">
<form-component
- v-if="canUpdate && showForm"
:form-state="formState"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
@@ -251,30 +272,37 @@ export default {
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
- <div v-else>
- <title-component
- :issuable-ref="issuableRef"
- :can-update="canUpdate"
- :title-html="state.titleHtml"
- :title-text="state.titleText"
- :show-inline-edit-button="showInlineEditButton"
- />
- <description-component
- v-if="state.descriptionHtml"
- :can-update="canUpdate"
- :description-html="state.descriptionHtml"
- :description-text="state.descriptionText"
- :updated-at="state.updatedAt"
- :task-status="state.taskStatus"
- :issuable-type="issuableType"
- :update-url="updateEndpoint"
- />
- <edited-component
- v-if="hasUpdated"
- :updated-at="state.updatedAt"
- :updated-by-name="state.updatedByName"
- :updated-by-path="state.updatedByPath"
- />
- </div>
+
+ <recaptcha-modal
+ v-show="showRecaptcha"
+ :html="recaptchaHTML"
+ @close="closeRecaptchaModal"
+ />
+ </div>
+ <div v-else>
+ <title-component
+ :issuable-ref="issuableRef"
+ :can-update="canUpdate"
+ :title-html="state.titleHtml"
+ :title-text="state.titleText"
+ :show-inline-edit-button="showInlineEditButton"
+ />
+ <description-component
+ v-if="state.descriptionHtml"
+ :can-update="canUpdate"
+ :description-html="state.descriptionHtml"
+ :description-text="state.descriptionText"
+ :updated-at="state.updatedAt"
+ :task-status="state.taskStatus"
+ :issuable-type="issuableType"
+ :update-url="updateEndpoint"
+ />
+ <edited-component
+ v-if="hasUpdated"
+ :updated-at="state.updatedAt"
+ :updated-by-name="state.updatedByName"
+ :updated-by-path="state.updatedByPath"
+ />
</div>
+</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index b7559ced946..c3f2bf130bb 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,9 +1,14 @@
<script>
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
+ import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
- mixins: [animateMixin],
+ mixins: [
+ animateMixin,
+ recaptchaModalImplementor,
+ ],
+
props: {
canUpdate: {
type: Boolean,
@@ -51,6 +56,7 @@
this.updateTaskStatusText();
},
},
+
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
@@ -61,9 +67,19 @@
dataType: this.issuableType,
fieldName: 'description',
selector: '.detail-page-description',
+ onSuccess: this.taskListUpdateSuccess.bind(this),
});
}
},
+
+ taskListUpdateSuccess(data) {
+ try {
+ this.checkForSpam(data);
+ } catch (error) {
+ if (error && error.name === 'SpamError') this.openRecaptcha();
+ }
+ },
+
updateTaskStatusText() {
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
const $issuableHeader = $('.issuable-meta');
@@ -109,5 +125,11 @@
:data-update-url="updateUrl"
>
</textarea>
+
+ <recaptcha-modal
+ v-show="showRecaptcha"
+ :html="recaptchaHTML"
+ @close="closeRecaptcha"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 52fe4ecd08b..4e577546551 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -53,7 +53,7 @@
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
- data-supports-quick-actionss="false"
+ data-supports-quick-actions="false"
aria-label="Description"
v-model="formState.description"
ref="textarea"
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index a21ce41e65e..7b762496ba5 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
- $('.issuable-edit').on('click', (e) => {
+ $('.js-issuable-edit').on('click', (e) => {
e.preventDefault();
eventHub.$emit('open.form');
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index cf8fda9a4fa..198a7823381 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,7 +1,9 @@
import _ from 'underscore';
+import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
+import { timeFor } from './lib/utils/datetime_utility';
export default class Job {
constructor(options) {
@@ -9,7 +11,7 @@ export default class Job {
this.state = null;
this.options = options || $('.js-build-options').data();
- this.pageUrl = this.options.pageUrl;
+ this.pagePath = this.options.pagePath;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
@@ -167,11 +169,11 @@ export default class Job {
getBuildTrace() {
return $.ajax({
- url: `${this.pageUrl}/trace.json`,
+ url: `${this.pagePath}/trace.json`,
data: { state: this.state },
})
.done((log) => {
- setCiStatusFavicon(`${this.pageUrl}/status.json`);
+ setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) {
this.state = log.state;
@@ -209,7 +211,7 @@ export default class Job {
}
if (log.status !== this.buildStatus) {
- gl.utils.visitUrl(this.pageUrl);
+ visitUrl(this.pagePath);
}
})
.fail(() => {
@@ -260,7 +262,7 @@ export default class Job {
if ($date.length) {
const date = $date.text();
return $date.text(
- gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
+ timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))),
);
}
}
diff --git a/app/assets/javascripts/lib/utils/cache.js b/app/assets/javascripts/lib/utils/cache.js
index 3141f1eeafc..596bd1e388a 100644
--- a/app/assets/javascripts/lib/utils/cache.js
+++ b/app/assets/javascripts/lib/utils/cache.js
@@ -1,4 +1,4 @@
-class Cache {
+export default class Cache {
constructor() {
this.internalStorage = { };
}
@@ -15,5 +15,3 @@ class Cache {
delete this.internalStorage[key];
}
}
-
-export default Cache;
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 33cc807912c..b5328c77b25 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,3 +1,4 @@
+import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => {
- let hash = window.gl.utils.getLocationHash();
+ let hash = getLocationHash();
if (!hash) return;
// This is required to handle non-unicode characters in hash
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index 7a72509d234..9a61003ef30 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -1,3 +1,2 @@
-/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
export const HIDDEN_CLASS = 'hidden';
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d0578b230b1..198b5164c92 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,9 +1,6 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
-
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
-
import {
lang,
s__,
@@ -12,121 +9,125 @@ import {
window.timeago = timeago;
window.dateFormat = dateFormat;
-(function() {
- (function(w) {
- var base;
- var timeagoInstance;
+/**
+ * Given a date object returns the day of the week in English
+ * @param {date} date
+ * @returns {String}
+ */
+export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
- if (w.gl == null) {
- w.gl = {};
- }
- if ((base = w.gl).utils == null) {
- base.utils = {};
- }
- w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+/**
+ * @example
+ * dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000"
+ * @param {date} datetime
+ * @returns {String}
+ */
+export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
- w.gl.utils.formatDate = function(datetime) {
- return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+let timeagoInstance;
+/**
+ * Sets a timeago Instance
+ */
+export function getTimeago() {
+ if (!timeagoInstance) {
+ const localeRemaining = function getLocaleRemaining(number, index) {
+ return [
+ [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')],
+ [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
+ [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
+ [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
+ [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
+ [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
+ [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
+ [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
+ [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
+ [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
+ [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
+ ][index];
};
-
- w.gl.utils.getDayName = function(date) {
- return this.days[date.getDay()];
+ const locale = function getLocale(number, index) {
+ return [
+ [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')],
+ [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
+ [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
+ [s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
+ [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
+ [s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
+ [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
+ [s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
+ [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
+ [s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
+ [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
+ ][index];
};
- w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
- $timeagoEls.each((i, el) => {
- if (setTimeago) {
- // Recreate with custom template
- $(el).tooltip({
- template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
- });
- }
+ timeago.register(lang, locale);
+ timeago.register(`${lang}-remaining`, localeRemaining);
+ timeagoInstance = timeago();
+ }
- el.classList.add('js-timeago-render');
- });
+ return timeagoInstance;
+}
- gl.utils.renderTimeago($timeagoEls);
- };
+/**
+ * For the given element, renders a timeago instance.
+ * @param {jQuery} $els
+ */
+export const renderTimeago = ($els) => {
+ const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
- w.gl.utils.getTimeago = function() {
- var locale;
-
- if (!timeagoInstance) {
- const localeRemaining = function(number, index) {
- return [
- [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')],
- [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
- [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
- [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
- [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
- [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
- [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
- [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
- [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
- [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')]
- ][index];
- };
- locale = function(number, index) {
- return [
- [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')],
- [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
- [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
- [s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
- [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
- [s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
- [s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
- [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
- [s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
- [s__('Timeago|%s years ago'), s__('Timeago|in %s years')]
- ][index];
- };
-
- timeago.register(lang, locale);
- timeago.register(`${lang}-remaining`, localeRemaining);
- timeagoInstance = timeago();
- }
-
- return timeagoInstance;
- };
+ // timeago.js sets timeouts internally for each timeago value to be updated in real time
+ getTimeago().render(timeagoEls, lang);
+};
- w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
- var timefor;
- if (!time) {
- return '';
- }
- if (new Date(time) < new Date()) {
- expiredLabel || (expiredLabel = s__('Timeago|Past due'));
- timefor = expiredLabel;
- } else {
- timefor = gl.utils.getTimeago().format(time, `${lang}-remaining`).trim();
- }
- return timefor;
- };
+/**
+ * For the given elements, sets a tooltip with a formatted date.
+ * @param {jQuery}
+ * @param {Boolean} setTimeago
+ */
+export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
+ $timeagoEls.each((i, el) => {
+ if (setTimeago) {
+ // Recreate with custom template
+ $(el).tooltip({
+ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ });
+ }
- w.gl.utils.renderTimeago = function($els) {
- const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
+ el.classList.add('js-timeago-render');
+ });
- // timeago.js sets timeouts internally for each timeago value to be updated in real time
- gl.utils.getTimeago().render(timeagoEls, lang);
- };
+ renderTimeago($timeagoEls);
+};
- w.gl.utils.getDayDifference = function(a, b) {
- var millisecondsPerDay = 1000 * 60 * 60 * 24;
- var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
- var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+/**
+ * Returns remaining or passed time over the given time.
+ * @param {*} time
+ * @param {*} expiredLabel
+ */
+export const timeFor = (time, expiredLabel) => {
+ if (!time) {
+ return '';
+ }
+ if (new Date(time) < new Date()) {
+ return expiredLabel || s__('Timeago|Past due');
+ }
+ return getTimeago().format(time, `${lang}-remaining`).trim();
+};
- return Math.floor((date2 - date1) / millisecondsPerDay);
- };
- })(window);
-}).call(window);
+export const getDayDifference = (a, b) => {
+ const millisecondsPerDay = 1000 * 60 * 60 * 24;
+ const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
+ const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+
+ return Math.floor((date2 - date1) / millisecondsPerDay);
+};
/**
* Port of ruby helper time_interval_in_words.
@@ -162,3 +163,10 @@ export function dateInWords(date, abbreviated = false) {
return `${monthName} ${date.getDate()}, ${year}`;
}
+
+window.gl = window.gl || {};
+window.gl.utils = {
+ ...(window.gl.utils || {}),
+ getTimeago,
+ localTimeAgo,
+};
diff --git a/app/assets/javascripts/lib/utils/tick_formats.js b/app/assets/javascripts/lib/utils/tick_formats.js
new file mode 100644
index 00000000000..0c10a85e336
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/tick_formats.js
@@ -0,0 +1,39 @@
+import { createDateTimeFormat } from '../../locale';
+
+let dateTimeFormats;
+
+export const initDateFormats = () => {
+ const dayFormat = createDateTimeFormat({ month: 'short', day: 'numeric' });
+ const monthFormat = createDateTimeFormat({ month: 'long' });
+ const yearFormat = createDateTimeFormat({ year: 'numeric' });
+
+ dateTimeFormats = {
+ dayFormat,
+ monthFormat,
+ yearFormat,
+ };
+};
+
+initDateFormats();
+
+/**
+ Formats a localized date in way that it can be used for d3.js axis.tickFormat().
+
+ That is, it displays
+ - 4-digit for first of January
+ - full month name for first of every month
+ - day and abbreviated month otherwise
+
+ see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
+ */
+export const dateTickFormat = (date) => {
+ if (date.getDate() !== 1) {
+ return dateTimeFormats.dayFormat.format(date);
+ }
+
+ if (date.getMonth() > 0) {
+ return dateTimeFormats.monthFormat.format(date);
+ }
+
+ return dateTimeFormats.yearFormat.format(date);
+};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 17236c91490..f1ee9c8f2e5 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,93 +1,69 @@
-/* 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) {
- w.gl = {};
-}
-if ((base = w.gl).utils == null) {
- base.utils = {};
-}
// Returns an array containing the value(s) of the
// of the key passed as an argument
-w.gl.utils.getParameterValues = function(sParam) {
- var i, sPageURL, sParameterName, sURLVariables, values;
- sPageURL = decodeURIComponent(window.location.search.substring(1));
- sURLVariables = sPageURL.split('&');
- sParameterName = void 0;
- values = [];
- i = 0;
- while (i < sURLVariables.length) {
- sParameterName = sURLVariables[i].split('=');
+export function getParameterValues(sParam) {
+ const sPageURL = decodeURIComponent(window.location.search.substring(1));
+
+ return sPageURL.split('&').reduce((acc, urlParam) => {
+ const sParameterName = urlParam.split('=');
+
if (sParameterName[0] === sParam) {
- values.push(sParameterName[1].replace(/\+/g, ' '));
+ acc.push(sParameterName[1].replace(/\+/g, ' '));
}
- i += 1;
- }
- return values;
-};
+
+ return acc;
+ }, []);
+}
+
// @param {Object} params - url keys and value to merge
// @param {String} url
-w.gl.utils.mergeUrlParams = function(params, url) {
- var lastChar, newUrl, paramName, paramValue, pattern;
- newUrl = decodeURIComponent(url);
- for (paramName in params) {
- paramValue = params[paramName];
- pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
- if (paramValue == null) {
- newUrl = newUrl.replace(pattern, '');
+export function mergeUrlParams(params, url) {
+ let newUrl = Object.keys(params).reduce((acc, paramName) => {
+ const paramValue = params[paramName];
+ const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`);
+
+ if (paramValue === null) {
+ return acc.replace(pattern, '');
} else if (url.search(pattern) !== -1) {
- newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
- } else {
- newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
+ return acc.replace(pattern, `$1${paramValue}$2`);
}
- }
+
+ return `${acc}${acc.indexOf('?') > 0 ? '&' : '?'}${paramName}=${paramValue}`;
+ }, decodeURIComponent(url));
+
// Remove a trailing ampersand
- lastChar = newUrl[newUrl.length - 1];
+ const lastChar = newUrl[newUrl.length - 1];
+
if (lastChar === '&') {
newUrl = newUrl.slice(0, -1);
}
+
return newUrl;
-};
-// removes parameter query string from url. returns the modified url
-w.gl.utils.removeParamQueryString = function(url, param) {
- var urlVariables, variables;
- url = decodeURIComponent(url);
- urlVariables = url.split('&');
- return ((function() {
- var j, len, results;
- results = [];
- for (j = 0, len = urlVariables.length; j < len; j += 1) {
- variables = urlVariables[j];
- if (variables.indexOf(param) === -1) {
- results.push(variables);
- }
- }
- return results;
- })()).join('&');
-};
-w.gl.utils.removeParams = (params) => {
+}
+
+export function removeParamQueryString(url, param) {
+ const decodedUrl = decodeURIComponent(url);
+ const urlVariables = decodedUrl.split('&');
+
+ return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
+}
+
+export function removeParams(params) {
const url = document.createElement('a');
url.href = window.location.href;
+
params.forEach((param) => {
- url.search = w.gl.utils.removeParamQueryString(url.search, param);
+ url.search = removeParamQueryString(url.search, param);
});
+
return url.href;
-};
-w.gl.utils.getLocationHash = function(url) {
- var hashIndex;
- if (typeof url === 'undefined') {
- // Note: We can't use window.location.hash here because it's
- // not consistent across browsers - Firefox will pre-decode it
- url = window.location.href;
- }
- hashIndex = url.indexOf('#');
- return hashIndex === -1 ? null : url.substring(hashIndex + 1);
-};
+}
-w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href);
+export function getLocationHash(url = window.location.href) {
+ const hashIndex = url.indexOf('#');
+
+ return hashIndex === -1 ? null : url.substring(hashIndex + 1);
+}
-// eslint-disable-next-line import/prefer-default-export
export function visitUrl(url, external = false) {
if (external) {
// Simulate `target="blank" rel="noopener noreferrer"`
@@ -100,12 +76,10 @@ export function visitUrl(url, external = false) {
}
}
+export function refreshCurrentPage() {
+ visitUrl(window.location.href);
+}
+
export function redirectTo(url) {
return window.location.assign(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 a75d1a4b8d0..fbd381d8ff7 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -175,4 +175,4 @@ LineHighlighter.prototype.__setLocationHash__ = function(value) {
}, document.title, value);
};
-window.LineHighlighter = LineHighlighter;
+export default LineHighlighter;
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index 1003b9ba0af..2f4328b56e1 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -1,8 +1,7 @@
import Jed from 'jed';
import sprintf from './sprintf';
-const langAttribute = document.querySelector('html').getAttribute('lang');
-const lang = (langAttribute || 'en').replace(/-/g, '_');
+const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en';
const locale = new Jed(window.translations || {});
delete window.translations;
@@ -47,9 +46,19 @@ const pgettext = (keyOrContext, key) => {
return translated[translated.length - 1];
};
-export { lang };
+/**
+ Creates an instance of Intl.DateTimeFormat for the current locale.
+
+ @param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
+ @returns {Intl.DateTimeFormat}
+*/
+const createDateTimeFormat =
+ formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions);
+
+export { languageCode };
export { gettext as __ };
export { ngettext as n__ };
export { pgettext as s__ };
export { sprintf };
+export { createDateTimeFormat };
export default locale;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index dcc0fa63b63..b984914ad68 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,6 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
/* global ConfirmDangerModal */
-/* global Aside */
import jQuery from 'jquery';
import _ from 'underscore';
@@ -28,50 +27,30 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
-import './lib/utils/datetime_utility';
-import './lib/utils/url_utility';
+import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
+import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
import './behaviors/';
// everything else
-import './activities';
-import './admin';
-import './aside';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
-import './commits';
-import './compare';
-import './compare_autocomplete';
import './confirm_danger_modal';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
-import './gl_field_error';
-import './gl_field_errors';
-import './gl_form';
import initTodoToggle from './header';
import initImporterStatus from './importer_status';
import './layout_nav';
import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
-import './merge_request';
import './merge_request_tabs';
import './milestone_select';
import './notes';
-import './notifications_dropdown';
-import './notifications_form';
-import './pager';
import './preview_markdown';
-import './project_find_file';
-import './project_import';
import './projects_dropdown';
-import './projects_list';
-import './syntax_highlight';
import './render_gfm';
-import './right_sidebar';
-import './search';
-import './search_autocomplete';
import initBreadcrumbs from './breadcrumb';
import './dispatcher';
@@ -122,13 +101,13 @@ $(function () {
// `hashchange` is not triggered when link target is already in window.location
$body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href');
- if (href.substr(1) === gl.utils.getLocationHash()) {
+ if (href.substr(1) === getLocationHash()) {
setTimeout(handleLocationHash, 1);
}
});
if (bootstrapBreakpoint === 'xs') {
- const $rightSidebar = $('aside.right-sidebar, .page-with-sidebar');
+ const $rightSidebar = $('aside.right-sidebar, .layout-page');
$rightSidebar
.removeClass('right-sidebar-expanded')
@@ -188,13 +167,13 @@ $(function () {
trigger: 'focus',
// set the viewport to the main content, excluding the navigation bar, so
// the navigation can't overlap the popover
- viewport: '.page-with-sidebar'
+ viewport: '.layout-page'
});
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
});
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+ localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons;
@@ -281,9 +260,8 @@ $(function () {
return fitSidebarForSize();
});
loadAwardsHandler();
- new Aside();
- gl.utils.renderTimeago();
+ renderTimeago();
$(document).trigger('init.scrolling-tabs');
@@ -294,7 +272,7 @@ $(function () {
const action = `${this.action}${link.search === '' ? '?' : '&'}`;
event.preventDefault();
- gl.utils.visitUrl(`${action}${$(this).serialize()}`);
+ visitUrl(`${action}${$(this).serialize()}`);
});
const flashContainer = document.querySelector('.flash-container');
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 17591829b76..94561d6b7c3 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -10,6 +10,7 @@ import './mixins/line_conflict_actions';
import './components/diff_file_editor';
import './components/inline_conflict_lines';
import './components/parallel_conflict_lines';
+import syntaxHighlight from '../syntax_highlight';
$(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive';
@@ -53,7 +54,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
- $('.js-syntax-highlight').syntaxHighlight();
+ syntaxHighlight($('.js-syntax-highlight'));
});
});
},
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index a9c08df4f93..cb3cdea8111 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,148 +1,143 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
-/* global MergeRequestTabs */
import 'vendor/jquery.waitforimages';
import TaskList from './task_list';
-import './merge_request_tabs';
+import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility';
-(function() {
- this.MergeRequest = (function() {
- function MergeRequest(opts) {
- // Initialize MergeRequest behavior
- //
- // Options:
- // action - String, current controller action
- //
- this.opts = opts != null ? opts : {};
- this.submitNoteForm = this.submitNoteForm.bind(this);
- this.$el = $('.merge-request');
- this.$('.show-all-commits').on('click', (function(_this) {
- return function() {
- return _this.showAllCommits();
- };
- })(this));
-
- this.initTabs();
- this.initMRBtnListeners();
- this.initCommitMessageListeners();
- this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
-
- if ($("a.btn-close").length) {
- this.taskList = new TaskList({
- dataType: 'merge_request',
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: (result) => {
- document.querySelector('#task_status').innerText = result.task_status;
- document.querySelector('#task_status_short').innerText = result.task_status_short;
- }
- });
- }
- }
-
- // Local jQuery finder
- MergeRequest.prototype.$ = function(selector) {
- return this.$el.find(selector);
+function MergeRequest(opts) {
+ // Initialize MergeRequest behavior
+ //
+ // Options:
+ // action - String, current controller action
+ //
+ this.opts = opts != null ? opts : {};
+ this.submitNoteForm = this.submitNoteForm.bind(this);
+ this.$el = $('.merge-request');
+ this.$('.show-all-commits').on('click', (function(_this) {
+ return function() {
+ return _this.showAllCommits();
};
-
- MergeRequest.prototype.initTabs = function() {
- if (window.mrTabs) {
- window.mrTabs.unbindEvents();
+ })(this));
+
+ this.initTabs();
+ this.initMRBtnListeners();
+ this.initCommitMessageListeners();
+ this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
+
+ if ($("a.btn-close").length) {
+ this.taskList = new TaskList({
+ dataType: 'merge_request',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
}
- window.mrTabs = new gl.MergeRequestTabs(this.opts);
- };
-
- MergeRequest.prototype.showAllCommits = function() {
- this.$('.first-commits').remove();
- return this.$('.all-commits').removeClass('hide');
- };
-
- MergeRequest.prototype.initMRBtnListeners = function() {
- var _this;
- _this = this;
- return $('a.btn-close, a.btn-reopen').on('click', function(e) {
- var $this, shouldSubmit;
- $this = $(this);
- shouldSubmit = $this.hasClass('btn-comment');
- if (shouldSubmit && $this.data('submitted')) {
- return;
- }
-
- if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
-
- if (shouldSubmit) {
- if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
- e.preventDefault();
- e.stopImmediatePropagation();
-
- _this.submitNoteForm($this.closest('form'), $this);
- }
- }
- });
- };
-
- MergeRequest.prototype.submitNoteForm = function(form, $button) {
- var noteText;
- noteText = form.find("textarea.js-note-text").val();
- if (noteText.trim().length > 0) {
- form.submit();
- $button.data('submitted', true);
- return $button.trigger('click');
- }
- };
-
- MergeRequest.prototype.initCommitMessageListeners = function() {
- $(document).on('click', 'a.js-with-description-link', function(e) {
- var textarea = $('textarea.js-commit-message');
- e.preventDefault();
+ });
+ }
+}
+
+// Local jQuery finder
+MergeRequest.prototype.$ = function(selector) {
+ return this.$el.find(selector);
+};
+
+MergeRequest.prototype.initTabs = function() {
+ if (window.mrTabs) {
+ window.mrTabs.unbindEvents();
+ }
+ window.mrTabs = new MergeRequestTabs(this.opts);
+};
+
+MergeRequest.prototype.showAllCommits = function() {
+ this.$('.first-commits').remove();
+ return this.$('.all-commits').removeClass('hide');
+};
+
+MergeRequest.prototype.initMRBtnListeners = function() {
+ var _this;
+ _this = this;
+ return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+ var $this, shouldSubmit;
+ $this = $(this);
+ shouldSubmit = $this.hasClass('btn-comment');
+ if (shouldSubmit && $this.data('submitted')) {
+ return;
+ }
- textarea.val(textarea.data('messageWithDescription'));
- $('.js-with-description-hint').hide();
- $('.js-without-description-hint').show();
- });
+ if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
- $(document).on('click', 'a.js-without-description-link', function(e) {
- var textarea = $('textarea.js-commit-message');
+ if (shouldSubmit) {
+ if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
+ e.stopImmediatePropagation();
- textarea.val(textarea.data('messageWithoutDescription'));
- $('.js-with-description-hint').show();
- $('.js-without-description-hint').hide();
- });
- };
-
- MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
- $('.detail-page-header .status-box')
- .removeClass(classToRemove)
- .addClass(classToAdd)
- .find('span')
- .text(newStatusText);
- };
-
- MergeRequest.prototype.decreaseCounter = function(by = 1) {
- const $el = $('.nav-links .js-merge-counter');
- const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
-
- $el.text(addDelimiter(count));
- };
-
- MergeRequest.prototype.hideCloseButton = function() {
- const el = document.querySelector('.merge-request .js-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');
+ _this.submitNoteForm($this.closest('form'), $this);
}
- // Dropdown for mobile screen
- el.querySelector('li.js-close-item').classList.add('hidden');
- };
-
- return MergeRequest;
- })();
-}).call(window);
+ }
+ });
+};
+
+MergeRequest.prototype.submitNoteForm = function(form, $button) {
+ var noteText;
+ noteText = form.find("textarea.js-note-text").val();
+ if (noteText.trim().length > 0) {
+ form.submit();
+ $button.data('submitted', true);
+ return $button.trigger('click');
+ }
+};
+
+MergeRequest.prototype.initCommitMessageListeners = function() {
+ $(document).on('click', 'a.js-with-description-link', function(e) {
+ var textarea = $('textarea.js-commit-message');
+ e.preventDefault();
+
+ textarea.val(textarea.data('messageWithDescription'));
+ $('.js-with-description-hint').hide();
+ $('.js-without-description-hint').show();
+ });
+
+ $(document).on('click', 'a.js-without-description-link', function(e) {
+ var textarea = $('textarea.js-commit-message');
+ e.preventDefault();
+
+ textarea.val(textarea.data('messageWithoutDescription'));
+ $('.js-with-description-hint').show();
+ $('.js-without-description-hint').hide();
+ });
+};
+
+MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) {
+ $('.detail-page-header .status-box')
+ .removeClass(classToRemove)
+ .addClass(classToAdd)
+ .find('span')
+ .text(newStatusText);
+};
+
+MergeRequest.prototype.decreaseCounter = function(by = 1) {
+ const $el = $('.nav-links .js-merge-counter');
+ const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
+
+ $el.text(addDelimiter(count));
+};
+
+MergeRequest.prototype.hideCloseButton = function() {
+ const el = document.querySelector('.merge-request .js-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');
+};
+
+export default MergeRequest;
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 54c1b7a268e..cacca35ca98 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -11,8 +11,11 @@ import {
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
+import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
+import { localTimeAgo } from './lib/utils/datetime_utility';
+import syntaxHighlight from './syntax_highlight';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -60,387 +63,382 @@ import Diff from './diff';
//
/* eslint-enable max-len */
-(() => {
- // Store the `location` object, allowing for easier stubbing in tests
- let location = window.location;
+// Store the `location` object, allowing for easier stubbing in tests
+let location = window.location;
- class MergeRequestTabs {
+export default class MergeRequestTabs {
- constructor({ action, setUrl, stubLocation } = {}) {
- const mergeRequestTabs = document.querySelector('.js-tabs-affix');
- const navbar = document.querySelector('.navbar-gitlab');
- const paddingTop = 16;
+ constructor({ action, setUrl, stubLocation } = {}) {
+ const mergeRequestTabs = document.querySelector('.js-tabs-affix');
+ const navbar = document.querySelector('.navbar-gitlab');
+ const paddingTop = 16;
- this.diffsLoaded = false;
- this.pipelinesLoaded = false;
- this.commitsLoaded = false;
- this.fixedLayoutPref = null;
+ this.diffsLoaded = false;
+ this.pipelinesLoaded = false;
+ this.commitsLoaded = false;
+ this.fixedLayoutPref = null;
- this.setUrl = setUrl !== undefined ? setUrl : true;
- this.setCurrentAction = this.setCurrentAction.bind(this);
- this.tabShown = this.tabShown.bind(this);
- this.showTab = this.showTab.bind(this);
- this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
+ this.setUrl = setUrl !== undefined ? setUrl : true;
+ this.setCurrentAction = this.setCurrentAction.bind(this);
+ this.tabShown = this.tabShown.bind(this);
+ this.showTab = this.showTab.bind(this);
+ this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
- if (mergeRequestTabs) {
- this.stickyTop += mergeRequestTabs.offsetHeight;
- }
-
- if (stubLocation) {
- location = stubLocation;
- }
+ if (mergeRequestTabs) {
+ this.stickyTop += mergeRequestTabs.offsetHeight;
+ }
- this.bindEvents();
- this.activateTab(action);
- this.initAffix();
+ if (stubLocation) {
+ location = stubLocation;
}
- bindEvents() {
- $(document)
- .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
- .on('click', '.js-show-tab', this.showTab);
+ this.bindEvents();
+ this.activateTab(action);
+ this.initAffix();
+ }
- $('.merge-request-tabs a[data-toggle="tab"]')
- .on('click', this.clickTab);
- }
+ bindEvents() {
+ $(document)
+ .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
+ .on('click', '.js-show-tab', this.showTab);
- // Used in tests
- unbindEvents() {
- $(document)
- .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
- .off('click', '.js-show-tab', this.showTab);
+ $('.merge-request-tabs a[data-toggle="tab"]')
+ .on('click', this.clickTab);
+ }
- $('.merge-request-tabs a[data-toggle="tab"]')
- .off('click', this.clickTab);
- }
+ // Used in tests
+ unbindEvents() {
+ $(document)
+ .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
+ .off('click', '.js-show-tab', this.showTab);
- destroyPipelinesView() {
- if (this.commitPipelinesTable) {
- this.commitPipelinesTable.$destroy();
- this.commitPipelinesTable = null;
+ $('.merge-request-tabs a[data-toggle="tab"]')
+ .off('click', this.clickTab);
+ }
- document.querySelector('#commit-pipeline-table-view').innerHTML = '';
- }
+ destroyPipelinesView() {
+ if (this.commitPipelinesTable) {
+ this.commitPipelinesTable.$destroy();
+ this.commitPipelinesTable = null;
+
+ document.querySelector('#commit-pipeline-table-view').innerHTML = '';
}
+ }
- showTab(e) {
+ showTab(e) {
+ e.preventDefault();
+ this.activateTab($(e.target).data('action'));
+ }
+
+ clickTab(e) {
+ if (e.currentTarget && isMetaClick(e)) {
+ const targetLink = e.currentTarget.getAttribute('href');
+ e.stopImmediatePropagation();
e.preventDefault();
- this.activateTab($(e.target).data('action'));
+ window.open(targetLink, '_blank');
}
+ }
- clickTab(e) {
- if (e.currentTarget && isMetaClick(e)) {
- const targetLink = e.currentTarget.getAttribute('href');
- e.stopImmediatePropagation();
- e.preventDefault();
- window.open(targetLink, '_blank');
+ tabShown(e) {
+ const $target = $(e.target);
+ const action = $target.data('action');
+
+ if (action === 'commits') {
+ this.loadCommits($target.attr('href'));
+ this.expandView();
+ this.resetViewContainer();
+ this.destroyPipelinesView();
+ } else if (this.isDiffAction(action)) {
+ this.loadDiff($target.attr('href'));
+ if (bp.getBreakpointSize() !== 'lg') {
+ this.shrinkView();
}
- }
-
- tabShown(e) {
- const $target = $(e.target);
- const action = $target.data('action');
-
- if (action === 'commits') {
- this.loadCommits($target.attr('href'));
- this.expandView();
- this.resetViewContainer();
- this.destroyPipelinesView();
- } else if (this.isDiffAction(action)) {
- this.loadDiff($target.attr('href'));
- if (bp.getBreakpointSize() !== 'lg') {
- this.shrinkView();
- }
- if (this.diffViewType() === 'parallel') {
- this.expandViewContainer();
- }
- this.destroyPipelinesView();
- } else if (action === 'pipelines') {
- this.resetViewContainer();
- this.mountPipelinesView();
- } else {
- if (bp.getBreakpointSize() !== 'xs') {
- this.expandView();
- }
- this.resetViewContainer();
- this.destroyPipelinesView();
-
- initDiscussionTab();
+ if (this.diffViewType() === 'parallel') {
+ this.expandViewContainer();
}
- if (this.setUrl) {
- this.setCurrentAction(action);
+ this.destroyPipelinesView();
+ } else if (action === 'pipelines') {
+ this.resetViewContainer();
+ this.mountPipelinesView();
+ } else {
+ if (bp.getBreakpointSize() !== 'xs') {
+ this.expandView();
}
+ this.resetViewContainer();
+ this.destroyPipelinesView();
+
+ initDiscussionTab();
+ }
+ if (this.setUrl) {
+ this.setCurrentAction(action);
}
+ }
- scrollToElement(container) {
- if (location.hash) {
- const offset = 0 - (
- $('.navbar-gitlab').outerHeight() +
- $('.js-tabs-affix').outerHeight()
- );
- const $el = $(`${container} ${location.hash}:not(.match)`);
- if ($el.length) {
- $.scrollTo($el[0], { offset });
- }
+ scrollToElement(container) {
+ if (location.hash) {
+ const offset = 0 - (
+ $('.navbar-gitlab').outerHeight() +
+ $('.js-tabs-affix').outerHeight()
+ );
+ const $el = $(`${container} ${location.hash}:not(.match)`);
+ if ($el.length) {
+ $.scrollTo($el[0], { offset });
}
}
+ }
- // Activate a tab based on the current action
- activateTab(action) {
- // important note: the .tab('show') method triggers 'shown.bs.tab' event itself
- $(`.merge-request-tabs a[data-action='${action}']`).tab('show');
+ // Activate a tab based on the current action
+ activateTab(action) {
+ // important note: the .tab('show') method triggers 'shown.bs.tab' event itself
+ $(`.merge-request-tabs a[data-action='${action}']`).tab('show');
+ }
+
+ // Replaces the current Merge Request-specific action in the URL with a new one
+ //
+ // If the action is "notes", the URL is reset to the standard
+ // `MergeRequests#show` route.
+ //
+ // Examples:
+ //
+ // location.pathname # => "/namespace/project/merge_requests/1"
+ // setCurrentAction('diffs')
+ // location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ //
+ // location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ // setCurrentAction('show')
+ // location.pathname # => "/namespace/project/merge_requests/1"
+ //
+ // location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ // setCurrentAction('commits')
+ // location.pathname # => "/namespace/project/merge_requests/1/commits"
+ //
+ // Returns the new URL String
+ setCurrentAction(action) {
+ this.currentAction = action;
+
+ // Remove a trailing '/commits' '/diffs' '/pipelines'
+ let newState = location.pathname.replace(/\/(commits|diffs|pipelines)(\.html)?\/?$/, '');
+
+ // Append the new action if we're on a tab other than 'notes'
+ if (this.currentAction !== 'show' && this.currentAction !== 'new') {
+ newState += `/${this.currentAction}`;
}
- // Replaces the current Merge Request-specific action in the URL with a new one
- //
- // If the action is "notes", the URL is reset to the standard
- // `MergeRequests#show` route.
- //
- // Examples:
- //
- // location.pathname # => "/namespace/project/merge_requests/1"
- // setCurrentAction('diffs')
- // location.pathname # => "/namespace/project/merge_requests/1/diffs"
- //
- // location.pathname # => "/namespace/project/merge_requests/1/diffs"
- // setCurrentAction('show')
- // location.pathname # => "/namespace/project/merge_requests/1"
- //
- // location.pathname # => "/namespace/project/merge_requests/1/diffs"
- // setCurrentAction('commits')
- // location.pathname # => "/namespace/project/merge_requests/1/commits"
- //
- // Returns the new URL String
- setCurrentAction(action) {
- this.currentAction = action;
+ // Ensure parameters and hash come along for the ride
+ newState += location.search + location.hash;
- // Remove a trailing '/commits' '/diffs' '/pipelines'
- let newState = location.pathname.replace(/\/(commits|diffs|pipelines)(\.html)?\/?$/, '');
+ // TODO: Consider refactoring in light of turbolinks removal.
- // Append the new action if we're on a tab other than 'notes'
- if (this.currentAction !== 'show' && this.currentAction !== 'new') {
- newState += `/${this.currentAction}`;
- }
+ // Replace the current history state with the new one without breaking
+ // Turbolinks' history.
+ //
+ // See https://github.com/rails/turbolinks/issues/363
+ window.history.replaceState({
+ url: newState,
+ }, document.title, newState);
- // Ensure parameters and hash come along for the ride
- newState += location.search + location.hash;
+ return newState;
+ }
- // TODO: Consider refactoring in light of turbolinks removal.
+ loadCommits(source) {
+ if (this.commitsLoaded) {
+ return;
+ }
+ this.ajaxGet({
+ url: `${source}.json`,
+ success: (data) => {
+ document.querySelector('div#commits').innerHTML = data.html;
+ localTimeAgo($('.js-timeago', 'div#commits'));
+ this.commitsLoaded = true;
+ this.scrollToElement('#commits');
+ },
+ });
+ }
- // Replace the current history state with the new one without breaking
- // Turbolinks' history.
- //
- // See https://github.com/rails/turbolinks/issues/363
- window.history.replaceState({
- url: newState,
- }, document.title, newState);
+ mountPipelinesView() {
+ const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
+ const CommitPipelinesTable = gl.CommitPipelinesTable;
+ this.commitPipelinesTable = new CommitPipelinesTable({
+ propsData: {
+ endpoint: pipelineTableViewEl.dataset.endpoint,
+ helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+ emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
+ errorStateSvgPath: pipelineTableViewEl.dataset.errorStateSvgPath,
+ autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath,
+ },
+ }).$mount();
+
+ // $mount(el) replaces the el with the new rendered component. We need it in order to mount
+ // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
+ pipelineTableViewEl.appendChild(this.commitPipelinesTable.$el);
+ }
- return newState;
+ loadDiff(source) {
+ if (this.diffsLoaded) {
+ document.dispatchEvent(new CustomEvent('scroll'));
+ return;
}
- loadCommits(source) {
- if (this.commitsLoaded) {
- return;
- }
- this.ajaxGet({
- url: `${source}.json`,
- success: (data) => {
- document.querySelector('div#commits').innerHTML = data.html;
- gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
- this.commitsLoaded = true;
- this.scrollToElement('#commits');
- },
- });
- }
+ // We extract pathname for the current Changes tab anchor href
+ // some pages like MergeRequestsController#new has query parameters on that anchor
+ const urlPathname = parseUrlPathname(source);
- mountPipelinesView() {
- const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
- const CommitPipelinesTable = gl.CommitPipelinesTable;
- this.commitPipelinesTable = new CommitPipelinesTable({
- propsData: {
- endpoint: pipelineTableViewEl.dataset.endpoint,
- helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
- emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath,
- errorStateSvgPath: pipelineTableViewEl.dataset.errorStateSvgPath,
- autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath,
- },
- }).$mount();
+ this.ajaxGet({
+ url: `${urlPathname}.json${location.search}`,
+ success: (data) => {
+ const $container = $('#diffs');
+ $container.html(data.html);
- // $mount(el) replaces the el with the new rendered component. We need it in order to mount
- // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
- pipelineTableViewEl.appendChild(this.commitPipelinesTable.$el);
- }
+ initChangesDropdown(this.stickyTop);
- loadDiff(source) {
- if (this.diffsLoaded) {
- document.dispatchEvent(new CustomEvent('scroll'));
- return;
- }
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ gl.diffNotesCompileComponents();
+ }
+
+ localTimeAgo($('.js-timeago', 'div#diffs'));
+ syntaxHighlight($('#diffs .js-syntax-highlight'));
- // We extract pathname for the current Changes tab anchor href
- // some pages like MergeRequestsController#new has query parameters on that anchor
- const urlPathname = parseUrlPathname(source);
-
- this.ajaxGet({
- url: `${urlPathname}.json${location.search}`,
- success: (data) => {
- const $container = $('#diffs');
- $container.html(data.html);
-
- initChangesDropdown(this.stickyTop);
-
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- gl.diffNotesCompileComponents();
- }
-
- gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
- $('#diffs .js-syntax-highlight').syntaxHighlight();
-
- if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
- this.expandViewContainer();
- }
- this.diffsLoaded = true;
-
- new Diff();
- this.scrollToElement('#diffs');
-
- $('.diff-file').each((i, el) => {
- new BlobForkSuggestion({
- openButtons: $(el).find('.js-edit-blob-link-fork-toggler'),
- forkButtons: $(el).find('.js-fork-suggestion-button'),
- cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
- suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
- actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
- })
- .init();
+ if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
+ this.expandViewContainer();
+ }
+ this.diffsLoaded = true;
+
+ new Diff();
+ this.scrollToElement('#diffs');
+
+ $('.diff-file').each((i, el) => {
+ new BlobForkSuggestion({
+ openButtons: $(el).find('.js-edit-blob-link-fork-toggler'),
+ forkButtons: $(el).find('.js-fork-suggestion-button'),
+ cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
+ suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
+ actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
+ })
+ .init();
+ });
+
+ // Scroll any linked note into view
+ // Similar to `toggler_behavior` in the discussion tab
+ const hash = getLocationHash();
+ const anchor = hash && $container.find(`.note[id="${hash}"]`);
+ if (anchor && anchor.length > 0) {
+ const notesContent = anchor.closest('.notes_content');
+ const lineType = notesContent.hasClass('new') ? 'new' : 'old';
+ notes.toggleDiffNote({
+ target: anchor,
+ lineType,
+ forceShow: true,
});
+ anchor[0].scrollIntoView();
+ handleLocationHash();
+ // We have multiple elements on the page with `#note_xxx`
+ // (discussion and diff tabs) and `:target` only applies to the first
+ anchor.addClass('target');
+ }
+ },
+ });
+ }
- // Scroll any linked note into view
- // Similar to `toggler_behavior` in the discussion tab
- const hash = window.gl.utils.getLocationHash();
- const anchor = hash && $container.find(`.note[id="${hash}"]`);
- if (anchor && anchor.length > 0) {
- const notesContent = anchor.closest('.notes_content');
- const lineType = notesContent.hasClass('new') ? 'new' : 'old';
- notes.toggleDiffNote({
- target: anchor,
- lineType,
- forceShow: true,
- });
- anchor[0].scrollIntoView();
- handleLocationHash();
- // We have multiple elements on the page with `#note_xxx`
- // (discussion and diff tabs) and `:target` only applies to the first
- anchor.addClass('target');
- }
- },
- });
- }
+ // Show or hide the loading spinner
+ //
+ // status - Boolean, true to show, false to hide
+ toggleLoading(status) {
+ $('.mr-loading-status .loading').toggle(status);
+ }
- // Show or hide the loading spinner
- //
- // status - Boolean, true to show, false to hide
- toggleLoading(status) {
- $('.mr-loading-status .loading').toggle(status);
- }
+ ajaxGet(options) {
+ const defaults = {
+ beforeSend: () => this.toggleLoading(true),
+ error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
+ complete: () => this.toggleLoading(false),
+ dataType: 'json',
+ type: 'GET',
+ };
+ $.ajax($.extend({}, defaults, options));
+ }
- ajaxGet(options) {
- const defaults = {
- beforeSend: () => this.toggleLoading(true),
- error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
- complete: () => this.toggleLoading(false),
- dataType: 'json',
- type: 'GET',
- };
- $.ajax($.extend({}, defaults, options));
- }
+ diffViewType() {
+ return $('.inline-parallel-buttons a.active').data('view-type');
+ }
- diffViewType() {
- return $('.inline-parallel-buttons a.active').data('view-type');
- }
+ isDiffAction(action) {
+ return action === 'diffs' || action === 'new/diffs';
+ }
- isDiffAction(action) {
- return action === 'diffs' || action === 'new/diffs';
+ expandViewContainer() {
+ const $wrapper = $('.content-wrapper .container-fluid').not('.breadcrumbs');
+ if (this.fixedLayoutPref === null) {
+ this.fixedLayoutPref = $wrapper.hasClass('container-limited');
}
+ $wrapper.removeClass('container-limited');
+ }
- expandViewContainer() {
- const $wrapper = $('.content-wrapper .container-fluid').not('.breadcrumbs');
- if (this.fixedLayoutPref === null) {
- this.fixedLayoutPref = $wrapper.hasClass('container-limited');
- }
- $wrapper.removeClass('container-limited');
+ resetViewContainer() {
+ if (this.fixedLayoutPref !== null) {
+ $('.content-wrapper .container-fluid')
+ .toggleClass('container-limited', this.fixedLayoutPref);
}
+ }
- resetViewContainer() {
- if (this.fixedLayoutPref !== null) {
- $('.content-wrapper .container-fluid')
- .toggleClass('container-limited', this.fixedLayoutPref);
- }
- }
+ shrinkView() {
+ const $gutterIcon = $('.js-sidebar-toggle i:visible');
- shrinkView() {
- const $gutterIcon = $('.js-sidebar-toggle i:visible');
+ // Wait until listeners are set
+ setTimeout(() => {
+ // Only when sidebar is expanded
+ if ($gutterIcon.is('.fa-angle-double-right')) {
+ $gutterIcon.closest('a').trigger('click', [true]);
+ }
+ }, 0);
+ }
- // Wait until listeners are set
- setTimeout(() => {
- // Only when sidebar is expanded
- if ($gutterIcon.is('.fa-angle-double-right')) {
- $gutterIcon.closest('a').trigger('click', [true]);
- }
- }, 0);
+ // Expand the issuable sidebar unless the user explicitly collapsed it
+ expandView() {
+ if (Cookies.get('collapsed_gutter') === 'true') {
+ return;
}
+ const $gutterIcon = $('.js-sidebar-toggle i:visible');
- // Expand the issuable sidebar unless the user explicitly collapsed it
- expandView() {
- if (Cookies.get('collapsed_gutter') === 'true') {
- return;
+ // Wait until listeners are set
+ setTimeout(() => {
+ // Only when sidebar is collapsed
+ if ($gutterIcon.is('.fa-angle-double-left')) {
+ $gutterIcon.closest('a').trigger('click', [true]);
}
- const $gutterIcon = $('.js-sidebar-toggle i:visible');
+ }, 0);
+ }
- // Wait until listeners are set
- setTimeout(() => {
- // Only when sidebar is collapsed
- if ($gutterIcon.is('.fa-angle-double-left')) {
- $gutterIcon.closest('a').trigger('click', [true]);
- }
- }, 0);
- }
+ initAffix() {
+ const $tabs = $('.js-tabs-affix');
+ const $fixedNav = $('.navbar-gitlab');
+
+ // Screen space on small screens is usually very sparse
+ // So we dont affix the tabs on these
+ if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
+
+ /**
+ If the browser does not support position sticky, it returns the position as static.
+ If the browser does support sticky, then we allow the browser to handle it, if not
+ then we default back to Bootstraps affix
+ **/
+ if ($tabs.css('position') !== 'static') return;
+
+ const $diffTabs = $('#diff-notes-app');
+
+ $tabs.off('affix.bs.affix affix-top.bs.affix')
+ .affix({
+ offset: {
+ top: () => (
+ $diffTabs.offset().top - $tabs.height() - $fixedNav.height()
+ ),
+ },
+ })
+ .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
+ .on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' }));
- initAffix() {
- const $tabs = $('.js-tabs-affix');
- const $fixedNav = $('.navbar-gitlab');
-
- // Screen space on small screens is usually very sparse
- // So we dont affix the tabs on these
- if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
-
- /**
- If the browser does not support position sticky, it returns the position as static.
- If the browser does support sticky, then we allow the browser to handle it, if not
- then we default back to Bootstraps affix
- **/
- if ($tabs.css('position') !== 'static') return;
-
- const $diffTabs = $('#diff-notes-app');
-
- $tabs.off('affix.bs.affix affix-top.bs.affix')
- .affix({
- offset: {
- top: () => (
- $diffTabs.offset().top - $tabs.height() - $fixedNav.height()
- ),
- },
- })
- .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
- .on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' }));
-
- // Fix bug when reloading the page already scrolling
- if ($tabs.hasClass('affix')) {
- $tabs.trigger('affix.bs.affix');
- }
+ // Fix bug when reloading the page already scrolling
+ if ($tabs.hasClass('affix')) {
+ $tabs.trigger('affix.bs.affix');
}
}
-
- window.gl = window.gl || {};
- window.gl.MergeRequestTabs = MergeRequestTabs;
-})();
+}
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 74e5a4f1cea..2e5e818d61d 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
+import { timeFor } from './lib/utils/datetime_utility';
(function() {
this.MilestoneSelect = (function() {
@@ -216,7 +217,7 @@ import _ from 'underscore';
$value.css('display', '');
if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path;
- data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
+ data.milestone.remaining = timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index cbe24c0915b..8da723ced03 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -21,6 +21,8 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
+ tagsPath: metricsData.tagsPath,
+ projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint,
emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
@@ -112,6 +114,8 @@
:hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
+ :project-path="projectPath"
+ :tags-path="tagsPath"
/>
</graph-group>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index f8782fde927..cdae287658b 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -30,6 +30,14 @@
required: false,
default: () => ({}),
},
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ tagsPath: {
+ type: String,
+ required: true,
+ },
},
mixins: [MonitoringMixin],
@@ -251,6 +259,14 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
+ <rect
+ class="prometheus-graph-overlay"
+ :width="(graphWidth - 70)"
+ :height="(graphHeight - 100)"
+ transform="translate(-5, 20)"
+ ref="graphOverlay"
+ @mousemove="handleMouseOverGraph($event)">
+ </rect>
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
@@ -267,14 +283,6 @@
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
/>
- <rect
- class="prometheus-graph-overlay"
- :width="(graphWidth - 70)"
- :height="(graphHeight - 100)"
- transform="translate(-5, 20)"
- ref="graphOverlay"
- @mousemove="handleMouseOverGraph($event)">
- </rect>
</svg>
</svg>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index e3b8be0c7fb..026e2fd0c49 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -1,5 +1,6 @@
<script>
- import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
+ import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
+ import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
@@ -25,6 +26,10 @@
},
},
+ components: {
+ Icon,
+ },
+
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
@@ -33,7 +38,7 @@
methods: {
refText(d) {
- return d.tag ? d.ref : d.sha.slice(0, 6);
+ return d.tag ? d.ref : d.sha.slice(0, 8);
},
formatTime(deploymentTime) {
@@ -41,7 +46,7 @@
},
formatDate(deploymentTime) {
- return dateFormat(deploymentTime);
+ return dateFormatWithName(deploymentTime);
},
nameDeploymentClass(deployment) {
@@ -54,11 +59,19 @@
positionFlag(deployment) {
let xPosition = 3;
- if (deployment.xPos > (this.graphWidth - 200)) {
- xPosition = -97;
+ if (deployment.xPos > (this.graphWidth - 225)) {
+ xPosition = -142;
}
return xPosition;
},
+
+ svgContainerHeight(tag) {
+ let svgHeight = 80;
+ if (!tag) {
+ svgHeight -= 20;
+ }
+ return svgHeight;
+ },
},
};
</script>
@@ -91,35 +104,75 @@
class="js-deploy-info-box"
:x="positionFlag(deployment)"
y="0"
- width="92"
- height="60">
+ width="134"
+ :height="svgContainerHeight(deployment.tag)">
<rect
class="rect-text-metric deploy-info-rect rect-metric"
x="1"
y="1"
rx="2"
- width="90"
- height="58">
+ width="132"
+ :height="svgContainerHeight(deployment.tag) - 2">
</rect>
- <g
- transform="translate(5, 2)">
- <text
- class="deploy-info-text text-metric-bold">
- {{refText(deployment)}}
- </text>
- </g>
- <text
- class="deploy-info-text"
- y="18"
- transform="translate(5, 2)">
- {{formatDate(deployment.time)}}
- </text>
<text
class="deploy-info-text text-metric-bold"
- y="38"
transform="translate(5, 2)">
- {{formatTime(deployment.time)}}
+ Deployed
</text>
+ <!--The date info-->
+ <g transform="translate(5, 20)">
+ <text class="deploy-info-text">
+ {{formatDate(deployment.time)}}
+ </text>
+ <text
+ class="deploy-info-text text-metric-bold"
+ x="62">
+ {{formatTime(deployment.time)}}
+ </text>
+ </g>
+ <line
+ class="divider-line"
+ x1="0"
+ y1="38"
+ x2="132"
+ :y2="38"
+ stroke="#000">
+ </line>
+ <!--Commit information-->
+ <g transform="translate(5, 40)">
+ <icon
+ name="commit"
+ :width="12"
+ :height="12"
+ :y="3">
+ </icon>
+ <a :xlink:href="deployment.commitUrl">
+ <text
+ class="deploy-info-text deploy-info-text-link"
+ transform="translate(20, 2)">
+ {{refText(deployment)}}
+ </text>
+ </a>
+ </g>
+ <!--Tag information-->
+ <g
+ transform="translate(5, 55)"
+ v-if="deployment.tag">
+ <icon
+ name="label"
+ :width="12"
+ :height="12"
+ :y="5">
+ </icon>
+ <a :xlink:href="deployment.tagUrl">
+ <text
+ class="deploy-info-text deploy-info-text-link"
+ transform="translate(20, 2)"
+ y="2">
+ {{deployment.tag}}
+ </text>
+ </a>
+ </g>
</svg>
</g>
<svg
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 31f38aca5d6..cbca14ede02 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -33,7 +33,9 @@ const mixins = {
id: deployment.id,
time,
sha: deployment.sha,
+ commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag,
+ tagUrl: `${this.tagsPath}/${deployment.tag}`,
ref: deployment.ref.name,
xPos,
showDeploymentFlag: false,
diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
index c4c6b1ac1f5..ad07a8465e2 100644
--- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js
+++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
@@ -1,6 +1,7 @@
import d3 from 'd3';
export const dateFormat = d3.time.format('%b %-d, %Y');
+export const dateFormatWithName = d3.time.format('%a, %b %-d');
export const timeFormat = d3.time.format('%-I:%M%p');
export const bisectDate = d3.bisector(d => d.time).left;
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 1d496c64e53..aa377327107 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api';
-import './lib/utils/url_utility';
+import { mergeUrlParams } from './lib/utils/url_utility';
export default class NamespaceSelect {
constructor(opts) {
@@ -50,7 +50,7 @@ export default class NamespaceSelect {
}
},
url(namespace) {
- return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
+ return mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
},
});
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index e1ab28978e8..042fe44e1c6 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
+import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
@@ -24,6 +25,7 @@ import Autosave from './autosave';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
+import { localTimeAgo } from './lib/utils/datetime_utility';
window.autosize = Autosize;
@@ -310,7 +312,7 @@ export default class Notes {
setupNewNote($note) {
// Update datetime format on the recent note
- gl.utils.localTimeAgo($note.find('.js-timeago'), false);
+ localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
@@ -330,7 +332,7 @@ export default class Notes {
}
static updateNoteTargetSelector($note) {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
// Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass);
@@ -462,7 +464,7 @@ export default class Notes {
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
- gl.utils.localTimeAgo($('.js-timeago'), false);
+ localTimeAgo($('.js-timeago'), false);
Notes.checkMergeRequestStatus();
return this.updateNotesCount(1);
}
diff --git a/app/assets/javascripts/notes/components/issue_comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 78986a450c2..e594377bc40 100644
--- a/app/assets/javascripts/notes/components/issue_comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -15,7 +15,7 @@
import issuableStateMixin from '../mixins/issuable_state';
export default {
- name: 'issueCommentForm',
+ name: 'commentForm',
data() {
return {
note: '',
diff --git a/app/assets/javascripts/notes/components/issue_note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index a16c5f6a785..ac4e1ffe53a 100644
--- a/app/assets/javascripts/notes/components/issue_note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -2,7 +2,7 @@
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
- import issueNoteForm from './issue_note_form.vue';
+ import noteForm from './note_form.vue';
import TaskList from '../../task_list';
import autosave from '../mixins/autosave';
@@ -29,7 +29,7 @@
noteEditedText,
noteAwardsList,
noteAttachment,
- issueNoteForm,
+ noteForm,
},
computed: {
noteBody() {
@@ -87,7 +87,7 @@
<div
v-html="note.note_html"
class="note-text md"></div>
- <issue-note-form
+ <note-form
v-if="isEditing"
ref="noteForm"
@handleFormUpdate="handleFormUpdate"
diff --git a/app/assets/javascripts/notes/components/issue_note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 4d527cb6643..4d527cb6643 100644
--- a/app/assets/javascripts/notes/components/issue_note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
diff --git a/app/assets/javascripts/notes/components/issue_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 460fde9b62a..11e8f805635 100644
--- a/app/assets/javascripts/notes/components/issue_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -2,12 +2,12 @@
import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
- import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+ import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
- import issueNoteForm from './issue_note_form.vue';
+ import noteForm from './note_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave';
@@ -25,12 +25,12 @@
};
},
components: {
- issueNote,
+ noteableNote,
userAvatarLink,
noteHeader,
noteSignedOutWidget,
noteEditedText,
- issueNoteForm,
+ noteForm,
placeholderNote,
placeholderSystemNote,
},
@@ -86,7 +86,7 @@
return placeholderNote;
}
- return issueNote;
+ return noteableNote;
},
componentData(note) {
return note.isPlaceholderNote ? note.notes[0] : note;
@@ -209,7 +209,7 @@
type="button"
class="js-vue-discussion-reply btn btn-text-field"
title="Add a reply">Reply...</button>
- <issue-note-form
+ <note-form
v-if="isReplying"
save-button-title="Comment"
:discussion="note"
diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 8c81c5d6df3..9186d6ff64a 100644
--- a/app/assets/javascripts/notes/components/issue_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,10 +1,11 @@
<script>
import { mapGetters, mapActions } from 'vuex';
+ import { escape } from 'underscore';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
- import issueNoteBody from './issue_note_body.vue';
+ import noteBody from './note_body.vue';
import eventHub from '../event_hub';
export default {
@@ -25,7 +26,7 @@
userAvatarLink,
noteHeader,
noteActions,
- issueNoteBody,
+ noteBody,
},
computed: {
...mapGetters([
@@ -85,7 +86,7 @@
};
this.isRequesting = true;
this.oldContent = this.note.note_html;
- this.note.note_html = noteText;
+ this.note.note_html = escape(noteText);
this.updateNote(data)
.then(() => {
@@ -122,9 +123,7 @@
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
- if (this.$refs.noteBody) {
- this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
- }
+ this.$refs.noteBody.$refs.noteForm.note = noteText;
},
},
created() {
@@ -173,7 +172,7 @@
@handleDelete="deleteHandler"
/>
</div>
- <issue-note-body
+ <note-body
:note="note"
:can-edit="note.current_user.can_edit"
:is-editing="isEditing"
diff --git a/app/assets/javascripts/notes/components/issue_notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 4cfcffa2391..c4cae4b3b6f 100644
--- a/app/assets/javascripts/notes/components/issue_notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,18 +1,19 @@
<script>
import { mapGetters, mapActions } from 'vuex';
+ import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import store from '../stores/';
import * as constants from '../constants';
- import issueNote from './issue_note.vue';
- import issueDiscussion from './issue_discussion.vue';
+ import noteableNote from './noteable_note.vue';
+ import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue';
- import issueCommentForm from './issue_comment_form.vue';
+ import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- name: 'issueNotesApp',
+ name: 'notesApp',
props: {
noteableData: {
type: Object,
@@ -35,10 +36,10 @@
};
},
components: {
- issueNote,
- issueDiscussion,
+ noteableNote,
+ noteableDiscussion,
systemNote,
- issueCommentForm,
+ commentForm,
loadingIcon,
placeholderNote,
placeholderSystemNote,
@@ -68,10 +69,10 @@
}
return placeholderNote;
} else if (note.individual_note) {
- return note.notes[0].system ? systemNote : issueNote;
+ return note.notes[0].system ? systemNote : noteableNote;
}
- return issueDiscussion;
+ return noteableDiscussion;
},
getComponentData(note) {
return note.individual_note ? note.notes[0] : note;
@@ -86,7 +87,7 @@
.then(() => this.checkLocationHash())
.catch(() => {
this.isLoading = false;
- Flash('Something went wrong while fetching issue comments. Please try again.');
+ Flash('Something went wrong while fetching comments. Please try again.');
});
},
initPolling() {
@@ -95,7 +96,7 @@
this.poll();
},
checkLocationHash() {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const element = document.getElementById(hash);
if (hash && element) {
@@ -146,6 +147,6 @@
/>
</ul>
- <issue-comment-form />
+ <comment-form />
</div>
</template>
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 8d74c5de5cf..d250dd8d25b 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,17 +1,25 @@
import Vue from 'vue';
-import issueNotesApp from './components/issue_notes_app.vue';
+import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes',
components: {
- issueNotesApp,
+ notesApp,
},
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
+ const parsedUserData = JSON.parse(notesDataset.currentUserData);
+ const currentUserData = parsedUserData ? {
+ id: parsedUserData.id,
+ name: parsedUserData.name,
+ username: parsedUserData.username,
+ avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
+ path: parsedUserData.path,
+ } : {};
return {
noteableData: JSON.parse(notesDataset.noteableData),
- currentUserData: JSON.parse(notesDataset.currentUserData),
+ currentUserData,
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath,
@@ -24,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
};
},
render(createElement) {
- return createElement('issue-notes-app', {
+ return createElement('notes-app', {
props: {
noteableData: this.noteableData,
notesData: this.notesData,
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index f90ac2d9f71..9570d1c00aa 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -1,31 +1,25 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */
import Flash from './flash';
-(function() {
- this.NotificationsDropdown = (function() {
- function NotificationsDropdown() {
- $(document).off('click', '.update-notification').on('click', '.update-notification', function(e) {
- var form, label, notificationLevel;
- e.preventDefault();
- if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
- return;
- }
- notificationLevel = $(this).data('notification-level');
- label = $(this).data('notification-title');
- form = $(this).parents('.notification-form:first');
- form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
- form.find('#notification_setting_level').val(notificationLevel);
- return form.submit();
- });
- $(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) {
- if (data.saved) {
- return $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
- } else {
- return new Flash('Failed to save new settings', 'alert');
- }
- });
+export default function notificationsDropdown() {
+ $(document).on('click', '.update-notification', function updateNotificationCallback(e) {
+ e.preventDefault();
+ if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
+ return;
}
- return NotificationsDropdown;
- })();
-}).call(window);
+ const notificationLevel = $(this).data('notification-level');
+ const form = $(this).parents('.notification-form:first');
+
+ form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
+ form.find('#notification_setting_level').val(notificationLevel);
+ form.submit();
+ });
+
+ $(document).on('ajax:success', '.notification-form', (e, data) => {
+ if (data.saved) {
+ $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
+ } else {
+ Flash('Failed to save new settings', 'alert');
+ }
+ });
+}
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 2ab9c4fed2c..4534360d577 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -1,55 +1,50 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, max-len */
-(function() {
- this.NotificationsForm = (function() {
- function NotificationsForm() {
- this.toggleCheckbox = this.toggleCheckbox.bind(this);
- this.removeEventListeners();
- this.initEventListeners();
- }
+export default class NotificationsForm {
+ constructor() {
+ this.toggleCheckbox = this.toggleCheckbox.bind(this);
+ this.initEventListeners();
+ }
- NotificationsForm.prototype.removeEventListeners = function() {
- return $(document).off('change', '.js-custom-notification-event');
- };
+ initEventListeners() {
+ $(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
+ }
- NotificationsForm.prototype.initEventListeners = function() {
- return $(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
- };
+ toggleCheckbox(e) {
+ const $checkbox = $(e.currentTarget);
+ const $parent = $checkbox.closest('.checkbox');
- NotificationsForm.prototype.toggleCheckbox = function(e) {
- var $checkbox, $parent;
- $checkbox = $(e.currentTarget);
- $parent = $checkbox.closest('.checkbox');
- return this.saveEvent($checkbox, $parent);
- };
+ this.saveEvent($checkbox, $parent);
+ }
- NotificationsForm.prototype.showCheckboxLoadingSpinner = function($parent) {
- return $parent.addClass('is-loading').find('.custom-notification-event-loading').removeClass('fa-check').addClass('fa-spin fa-spinner').removeClass('is-done');
- };
+ // eslint-disable-next-line class-methods-use-this
+ showCheckboxLoadingSpinner($parent) {
+ $parent.addClass('is-loading')
+ .find('.custom-notification-event-loading')
+ .removeClass('fa-check')
+ .addClass('fa-spin fa-spinner')
+ .removeClass('is-done');
+ }
- NotificationsForm.prototype.saveEvent = function($checkbox, $parent) {
- var form;
- form = $parent.parents('form:first');
- return $.ajax({
- url: form.attr('action'),
- method: form.attr('method'),
- dataType: 'json',
- data: form.serialize(),
- beforeSend: (function(_this) {
- return function() {
- return _this.showCheckboxLoadingSpinner($parent);
- };
- })(this)
- }).done(function(data) {
- $checkbox.enable();
- if (data.saved) {
- $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
- return setTimeout(function() {
- return $parent.removeClass('is-loading').find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
- }, 2000);
- }
- });
- };
+ saveEvent($checkbox, $parent) {
+ const form = $parent.parents('form:first');
- return NotificationsForm;
- })();
-}).call(window);
+ return $.ajax({
+ url: form.attr('action'),
+ method: form.attr('method'),
+ dataType: 'json',
+ data: form.serialize(),
+ beforeSend: () => {
+ this.showCheckboxLoadingSpinner($parent);
+ },
+ }).done((data) => {
+ $checkbox.enable();
+ if (data.saved) {
+ $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+ setTimeout(() => {
+ $parent.removeClass('is-loading')
+ .find('.custom-notification-event-loading')
+ .toggleClass('fa-spin fa-spinner fa-check is-done');
+ }, 2000);
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index e3fc1e2fc2f..6552a88b606 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,78 +1,74 @@
import { getParameterByName } from '~/lib/utils/common_utils';
-import '~/lib/utils/url_utility';
+import { removeParams } from './lib/utils/url_utility';
-(() => {
- const ENDLESS_SCROLL_BOTTOM_PX = 400;
- const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
+const ENDLESS_SCROLL_BOTTOM_PX = 400;
+const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
- const Pager = {
- init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
- this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
- this.limit = limit;
- this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
- this.disable = disable;
- this.prepareData = prepareData;
- this.callback = callback;
- this.loading = $('.loading').first();
- if (preload) {
- this.offset = 0;
- this.getOld();
- }
- this.initLoadMore();
- },
+export default {
+ init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
+ this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
+ this.limit = limit;
+ this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
+ this.disable = disable;
+ this.prepareData = prepareData;
+ this.callback = callback;
+ this.loading = $('.loading').first();
+ if (preload) {
+ this.offset = 0;
+ this.getOld();
+ }
+ this.initLoadMore();
+ },
- getOld() {
- this.loading.show();
- $.ajax({
- type: 'GET',
- url: this.url,
- data: `limit=${this.limit}&offset=${this.offset}`,
- dataType: 'json',
- error: () => this.loading.hide(),
- success: (data) => {
- this.append(data.count, this.prepareData(data.html));
- this.callback();
+ getOld() {
+ this.loading.show();
+ $.ajax({
+ type: 'GET',
+ url: this.url,
+ data: `limit=${this.limit}&offset=${this.offset}`,
+ dataType: 'json',
+ error: () => this.loading.hide(),
+ success: (data) => {
+ this.append(data.count, this.prepareData(data.html));
+ this.callback();
- // keep loading until we've filled the viewport height
- if (!this.disable && !this.isScrollable()) {
- this.getOld();
- } else {
- this.loading.hide();
- }
- },
- });
- },
+ // keep loading until we've filled the viewport height
+ if (!this.disable && !this.isScrollable()) {
+ this.getOld();
+ } else {
+ this.loading.hide();
+ }
+ },
+ });
+ },
- append(count, html) {
- $('.content_list').append(html);
- if (count > 0) {
- this.offset += count;
- } else {
- this.disable = true;
- }
- },
+ append(count, html) {
+ $('.content_list').append(html);
+ if (count > 0) {
+ this.offset += count;
+ } else {
+ this.disable = true;
+ }
+ },
- isScrollable() {
- const $w = $(window);
- return $(document).height() > $w.height() + $w.scrollTop() + ENDLESS_SCROLL_BOTTOM_PX;
- },
+ isScrollable() {
+ const $w = $(window);
+ return $(document).height() > $w.height() + $w.scrollTop() + ENDLESS_SCROLL_BOTTOM_PX;
+ },
- initLoadMore() {
- $(document).unbind('scroll');
- $(document).endlessScroll({
- bottomPixels: ENDLESS_SCROLL_BOTTOM_PX,
- fireDelay: ENDLESS_SCROLL_FIRE_DELAY_MS,
- fireOnce: true,
- ceaseFire: () => this.disable === true,
- callback: () => {
- if (!this.loading.is(':visible')) {
- this.loading.show();
- this.getOld();
- }
- },
- });
- },
- };
-
- window.Pager = Pager;
-})();
+ initLoadMore() {
+ $(document).unbind('scroll');
+ $(document).endlessScroll({
+ bottomPixels: ENDLESS_SCROLL_BOTTOM_PX,
+ fireDelay: ENDLESS_SCROLL_FIRE_DELAY_MS,
+ fireOnce: true,
+ ceaseFire: () => this.disable === true,
+ callback: () => {
+ if (!this.loading.is(':visible')) {
+ this.loading.show();
+ this.getOld();
+ }
+ },
+ });
+ },
+};
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
index 9bbdf7f513c..0562a681c4b 100644
--- a/app/assets/javascripts/performance_bar.js
+++ b/app/assets/javascripts/performance_bar.js
@@ -1,5 +1,6 @@
import 'vendor/peek';
import 'vendor/peek.performance_bar';
+import { getParameterValues } from './lib/utils/url_utility';
export default class PerformanceBar {
constructor(opts) {
@@ -39,7 +40,7 @@ export default class PerformanceBar {
}
handleLineProfileLink(e) {
- const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler');
+ const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
const shouldToggleModal = lineProfilerParameter.length > 0 &&
lineProfilerParameterRegex.test(e.currentTarget.href);
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index 0eaac8dd64f..78322f30685 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
@@ -1,36 +1,41 @@
<script>
-export default {
- props: {
- helpPagePath: {
- type: String,
- required: true,
+ export default {
+ props: {
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
},
- emptyStateSvgPath: {
- type: String,
- required: true,
- },
- },
-};
+ };
</script>
-
<template>
<div class="row empty-state js-empty-state">
<div class="col-xs-12">
- <div class="svg-content">
- <img :src="emptyStateSvgPath"/>
+ <div class="svg-content svg-250">
+ <img :src="emptyStateSvgPath" />
</div>
</div>
- <div class="col-xs-12 text-center">
+ <div class="col-xs-12">
<div class="text-content">
- <h4>Build with confidence</h4>
+ <h4 class="text-center">
+ {{ s__("Pipelines|Build with confidence") }}
+ </h4>
<p>
- Continous Integration can help catch bugs by running your tests automatically,
- while Continuous Deployment can help you deliver code to your product environment.
+ {{ s__("Pipelines|Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.") }}
</p>
- <a :href="helpPagePath" class="btn btn-info">
- Get started with Pipelines
- </a>
+ <div class="text-center">
+ <a
+ :href="helpPagePath"
+ class="btn btn-info"
+ >
+ {{ s__("Pipelines|Get started with Pipelines") }}
+ </a>
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 08199b4234a..b01c799643c 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -59,8 +59,26 @@
},
computed: {
+ status() {
+ return this.job && this.job.status ? this.job.status : {};
+ },
+
tooltipText() {
- return `${this.job.name} - ${this.job.status.label}`;
+ const textBuilder = [];
+
+ if (this.job.name) {
+ textBuilder.push(this.job.name);
+ }
+
+ if (this.job.name && this.status.label) {
+ textBuilder.push('-');
+ }
+
+ if (this.status.label) {
+ textBuilder.push(`${this.job.status.label}`);
+ }
+
+ return textBuilder.join(' ');
},
/**
@@ -78,8 +96,8 @@
<div class="ci-job-component">
<a
v-tooltip
- v-if="job.status.has_details"
- :href="job.status.details_path"
+ v-if="status.has_details"
+ :href="status.details_path"
:title="tooltipText"
:class="cssClassJobName"
data-container="body"
@@ -95,6 +113,7 @@
<div
v-else
v-tooltip
+ class="js-job-component-tooltip"
:title="tooltipText"
:class="cssClassJobName"
data-container="body"
@@ -108,18 +127,18 @@
<action-component
v-if="hasAction && !isDropdown"
- :tooltip-text="job.status.action.title"
- :link="job.status.action.path"
- :action-icon="job.status.action.icon"
- :action-method="job.status.action.method"
+ :tooltip-text="status.action.title"
+ :link="status.action.path"
+ :action-icon="status.action.icon"
+ :action-method="status.action.method"
/>
<dropdown-action-component
v-if="hasAction && isDropdown"
- :tooltip-text="job.status.action.title"
- :link="job.status.action.path"
- :action-icon="job.status.action.icon"
- :action-method="job.status.action.method"
+ :tooltip-text="status.action.title"
+ :link="status.action.path"
+ :action-icon="status.action.icon"
+ :action-method="status.action.method"
/>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/pipelines_bundle.js b/app/assets/javascripts/pipelines/pipelines_bundle.js
index 923d9bfb248..3e4b6eeb5bf 100644
--- a/app/assets/javascripts/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/pipelines/pipelines_bundle.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
import PipelinesStore from './stores/pipelines_store';
import pipelinesComponent from './components/pipelines.vue';
+import Translate from '../vue_shared/translate';
+
+Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipelines-list-vue',
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index 6348a2e331d..78be6b6e884 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -1,5 +1,5 @@
<script>
- import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
+ import modal from '../../../vue_shared/components/modal.vue';
import { __, s__, sprintf } from '../../../locale';
import csrf from '../../../lib/utils/csrf';
@@ -26,7 +26,7 @@
};
},
components: {
- popupDialog,
+ modal,
},
computed: {
csrfToken() {
@@ -89,7 +89,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
<template>
<div>
- <popup-dialog
+ <modal
v-if="isOpen"
:title="s__('Profiles|Delete your account?')"
:text="text"
@@ -134,7 +134,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
</form>
</template>
- </popup-dialog>
+ </modal>
<button
type="button"
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 3131e71d9d6..d4f26b81f30 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie';
+import { visitUrl } from './lib/utils/url_utility';
import projectSelect from './project_select';
export default class Project {
@@ -122,7 +123,7 @@ export default class Project {
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) {
- gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`);
+ visitUrl(`${action}${divider}${$form.serialize()}`);
}
}
},
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 19682b20a4a..0da32b4a3cc 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -2,169 +2,163 @@
import fuzzaldrinPlus from 'fuzzaldrin-plus';
-(function() {
- this.ProjectFindFile = (function() {
- var highlighter;
-
- function ProjectFindFile(element1, options) {
- this.element = element1;
- this.options = options;
- this.goToBlob = this.goToBlob.bind(this);
- this.goToTree = this.goToTree.bind(this);
- this.selectRowDown = this.selectRowDown.bind(this);
- this.selectRowUp = this.selectRowUp.bind(this);
- this.filePaths = {};
- this.inputElement = this.element.find(".file-finder-input");
- // init event
- this.initEvent();
- // focus text input box
- this.inputElement.focus();
- // load file list
- this.load(this.options.url);
+// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
+const highlighter = function(element, text, matches) {
+ var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
+ lastIndex = 0;
+ highlightText = "";
+ matchedChars = [];
+ for (j = 0, len = matches.length; j < len; j += 1) {
+ matchIndex = matches[j];
+ unmatched = text.substring(lastIndex, matchIndex);
+ if (unmatched) {
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ matchedChars = [];
+ element.append(document.createTextNode(unmatched));
}
-
- ProjectFindFile.prototype.initEvent = function() {
- this.inputElement.off("keyup");
- this.inputElement.on("keyup", (function(_this) {
- return function(event) {
- var oldValue, ref, target, value;
- target = $(event.target);
- value = target.val();
- oldValue = (ref = target.data("oldValue")) != null ? ref : "";
- if (value !== oldValue) {
- target.data("oldValue", value);
- _this.findFile();
- return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
- }
- };
- })(this));
- };
-
- ProjectFindFile.prototype.findFile = function() {
- var result, searchText;
- searchText = this.inputElement.val();
- result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
- return this.renderList(result, searchText);
- // find file
- };
+ matchedChars.push(text[matchIndex]);
+ lastIndex = matchIndex + 1;
+ }
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ return element.append(document.createTextNode(text.substring(lastIndex)));
+};
+
+export default class ProjectFindFile {
+ constructor(element1, options) {
+ this.element = element1;
+ this.options = options;
+ this.goToBlob = this.goToBlob.bind(this);
+ this.goToTree = this.goToTree.bind(this);
+ this.selectRowDown = this.selectRowDown.bind(this);
+ this.selectRowUp = this.selectRowUp.bind(this);
+ this.filePaths = {};
+ this.inputElement = this.element.find(".file-finder-input");
+ // init event
+ this.initEvent();
+ // focus text input box
+ this.inputElement.focus();
+ // load file list
+ this.load(this.options.url);
+ }
+
+ initEvent() {
+ this.inputElement.off("keyup");
+ this.inputElement.on("keyup", (function(_this) {
+ return function(event) {
+ var oldValue, ref, target, value;
+ target = $(event.target);
+ value = target.val();
+ oldValue = (ref = target.data("oldValue")) != null ? ref : "";
+ if (value !== oldValue) {
+ target.data("oldValue", value);
+ _this.findFile();
+ return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
+ }
+ };
+ })(this));
+ }
+
+ findFile() {
+ var result, searchText;
+ searchText = this.inputElement.val();
+ result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
+ return this.renderList(result, searchText);
+ // find file
+ }
// files pathes load
- ProjectFindFile.prototype.load = function(url) {
- return $.ajax({
- url: url,
- method: "get",
- dataType: "json",
- success: (function(_this) {
- return function(data) {
- _this.element.find(".loading").hide();
- _this.filePaths = data;
- _this.findFile();
- return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
- };
- })(this)
- });
- };
+ load(url) {
+ return $.ajax({
+ url: url,
+ method: "get",
+ dataType: "json",
+ success: (function(_this) {
+ return function(data) {
+ _this.element.find(".loading").hide();
+ _this.filePaths = data;
+ _this.findFile();
+ return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
+ };
+ })(this)
+ });
+ }
// render result
- ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
- var blobItemUrl, filePath, html, i, j, len, matches, results;
- this.element.find(".tree-table > tbody").empty();
- results = [];
- for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) {
- filePath = filePaths[i];
- if (i === 20) {
- break;
- }
- if (searchText) {
- matches = fuzzaldrinPlus.match(filePath, searchText);
- }
- blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
- html = this.makeHtml(filePath, matches, blobItemUrl);
- results.push(this.element.find(".tree-table > tbody").append(html));
- }
- return results;
- };
-
- // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
- highlighter = function(element, text, matches) {
- var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
- lastIndex = 0;
- highlightText = "";
- matchedChars = [];
- for (j = 0, len = matches.length; j < len; j += 1) {
- matchIndex = matches[j];
- unmatched = text.substring(lastIndex, matchIndex);
- if (unmatched) {
- if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
- }
- matchedChars = [];
- element.append(document.createTextNode(unmatched));
- }
- matchedChars.push(text[matchIndex]);
- lastIndex = matchIndex + 1;
- }
- if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
+ renderList(filePaths, searchText) {
+ var blobItemUrl, filePath, html, i, j, len, matches, results;
+ this.element.find(".tree-table > tbody").empty();
+ results = [];
+ for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) {
+ filePath = filePaths[i];
+ if (i === 20) {
+ break;
}
- return element.append(document.createTextNode(text.substring(lastIndex)));
- };
-
- // make tbody row html
- ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
- var $tr;
- $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
- if (matches) {
- $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
- } else {
- $tr.find("a").attr("href", blobItemUrl);
- $tr.find(".str-truncated").text(filePath);
+ if (searchText) {
+ matches = fuzzaldrinPlus.match(filePath, searchText);
}
- return $tr;
- };
-
- ProjectFindFile.prototype.selectRow = function(type) {
- var next, rows, selectedRow;
- rows = this.element.find(".files-slider tr.tree-item");
- selectedRow = this.element.find(".files-slider tr.tree-item.selected");
- if (rows && rows.length > 0) {
- if (selectedRow && selectedRow.length > 0) {
- if (type === "UP") {
- next = selectedRow.prev();
- } else if (type === "DOWN") {
- next = selectedRow.next();
- }
- if (next.length > 0) {
- selectedRow.removeClass("selected");
- selectedRow = next;
- }
- } else {
- selectedRow = rows.eq(0);
+ blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
+ html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
+ results.push(this.element.find(".tree-table > tbody").append(html));
+ }
+ return results;
+ }
+
+ // make tbody row html
+ static makeHtml(filePath, matches, blobItemUrl) {
+ var $tr;
+ $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
+ if (matches) {
+ $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
+ } else {
+ $tr.find("a").attr("href", blobItemUrl);
+ $tr.find(".str-truncated").text(filePath);
+ }
+ return $tr;
+ }
+
+ selectRow(type) {
+ var next, rows, selectedRow;
+ rows = this.element.find(".files-slider tr.tree-item");
+ selectedRow = this.element.find(".files-slider tr.tree-item.selected");
+ if (rows && rows.length > 0) {
+ if (selectedRow && selectedRow.length > 0) {
+ if (type === "UP") {
+ next = selectedRow.prev();
+ } else if (type === "DOWN") {
+ next = selectedRow.next();
+ }
+ if (next.length > 0) {
+ selectedRow.removeClass("selected");
+ selectedRow = next;
}
- return selectedRow.addClass("selected").focus();
+ } else {
+ selectedRow = rows.eq(0);
}
- };
-
- ProjectFindFile.prototype.selectRowUp = function() {
- return this.selectRow("UP");
- };
+ return selectedRow.addClass("selected").focus();
+ }
+ }
- ProjectFindFile.prototype.selectRowDown = function() {
- return this.selectRow("DOWN");
- };
+ selectRowUp() {
+ return this.selectRow("UP");
+ }
- ProjectFindFile.prototype.goToTree = function() {
- return location.href = this.options.treeUrl;
- };
+ selectRowDown() {
+ return this.selectRow("DOWN");
+ }
- ProjectFindFile.prototype.goToBlob = function() {
- var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
+ goToTree() {
+ return location.href = this.options.treeUrl;
+ }
- if ($link.length) {
- $link.get(0).click();
- }
- };
+ goToBlob() {
+ var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
- return ProjectFindFile;
- })();
-}).call(window);
+ if ($link.length) {
+ $link.get(0).click();
+ }
+ }
+}
diff --git a/app/assets/javascripts/project_variables.js b/app/assets/javascripts/project_variables.js
deleted file mode 100644
index 567c311f119..00000000000
--- a/app/assets/javascripts/project_variables.js
+++ /dev/null
@@ -1,39 +0,0 @@
-
-const HIDDEN_VALUE_TEXT = '******';
-
-export default class ProjectVariables {
- constructor() {
- this.$revealBtn = $('.js-btn-toggle-reveal-values');
- this.$revealBtn.on('click', this.toggleRevealState.bind(this));
- }
-
- toggleRevealState(e) {
- e.preventDefault();
-
- const oldStatus = this.$revealBtn.attr('data-status');
- let newStatus = 'hidden';
- let newAction = 'Reveal Values';
-
- if (oldStatus === 'hidden') {
- newStatus = 'revealed';
- newAction = 'Hide Values';
- }
-
- this.$revealBtn.attr('data-status', newStatus);
-
- const $variables = $('.variable-value');
-
- $variables.each((_, variable) => {
- const $variable = $(variable);
- let newText = HIDDEN_VALUE_TEXT;
-
- if (newStatus === 'revealed') {
- newText = $variable.attr('data-value');
- }
-
- $variable.text(newText);
- });
-
- this.$revealBtn.text(newAction);
- }
-}
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index c34927499fc..cec6f0dd5a3 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,7 +1,7 @@
-import '../lib/utils/url_utility';
+import { getParameterValues } from '../lib/utils/url_utility';
const bindEvents = () => {
- const path = gl.utils.getParameterValues('path')[0];
+ const path = getParameterValues('path')[0];
// get the path url and append it in the inputS
$('.js-path-name').val(path);
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index c91a0d9ba41..5482c55f8bb 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -1,12 +1,12 @@
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
-
+import syntaxHighlight from './syntax_highlight';
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math & mermaid diagrams.
//
$.fn.renderGFM = function renderGFM() {
- this.find('.js-syntax-highlight').syntaxHighlight();
+ syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
return this;
diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/repo/components/new_dropdown/modal.vue
index ac1f613bb71..c191af7dec3 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/repo/components/new_dropdown/modal.vue
@@ -1,7 +1,7 @@
<script>
import { mapActions } from 'vuex';
import { __ } from '../../../locale';
- import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
+ import modal from '../../../vue_shared/components/modal.vue';
export default {
props: {
@@ -20,7 +20,7 @@
};
},
components: {
- popupDialog,
+ modal,
},
methods: {
...mapActions([
@@ -68,7 +68,7 @@
</script>
<template>
- <popup-dialog
+ <modal
:title="modalTitle"
:primary-button-label="buttonLabel"
kind="success"
@@ -94,5 +94,5 @@
</div>
</fieldset>
</form>
- </popup-dialog>
+ </modal>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index d3344d0c8dc..4e0178072cb 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -2,12 +2,12 @@
import { mapGetters, mapState, mapActions } from 'vuex';
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
-import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
import commitFilesList from './commit_sidebar/list.vue';
export default {
components: {
- PopupDialog,
+ modal,
icon,
commitFilesList,
},
@@ -16,7 +16,7 @@ export default {
},
data() {
return {
- showNewBranchDialog: false,
+ showNewBranchModal: false,
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
@@ -58,7 +58,7 @@ export default {
start_branch: createNewBranch ? this.currentBranch : undefined,
};
- this.showNewBranchDialog = false;
+ this.showNewBranchModal = false;
this.submitCommitsLoading = true;
this.commitChanges({ payload, newMr: this.startNewMR })
@@ -76,7 +76,7 @@ export default {
this.checkCommitStatus()
.then((branchChanged) => {
if (branchChanged) {
- this.showNewBranchDialog = true;
+ this.showNewBranchModal = true;
} else {
this.makeCommit();
}
@@ -99,13 +99,13 @@ export default {
'is-collapsed': collapsed,
}"
>
- <popup-dialog
- v-if="showNewBranchDialog"
+ <modal
+ v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
- @toggle="showNewBranchDialog = false"
+ @toggle="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
<button
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index 6c1bb4b8566..37bd9003e96 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -1,10 +1,10 @@
<script>
import { mapGetters, mapActions, mapState } from 'vuex';
-import popupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
export default {
components: {
- popupDialog,
+ modal,
},
computed: {
...mapState([
@@ -43,7 +43,7 @@ export default {
{{buttonLabel}}
</span>
</button>
- <popup-dialog
+ <modal
v-if="discardPopupOpen"
class="text-left"
:primary-button-label="__('Discard changes')"
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index 6ce9267f598..3d1e0297bd5 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -1,6 +1,7 @@
<script>
-/* global LineHighlighter */
import { mapGetters } from 'vuex';
+import LineHighlighter from '../../line_highlighter';
+import syntaxHighlight from '../../syntax_highlight';
export default {
computed: {
@@ -13,7 +14,7 @@ export default {
},
methods: {
highlightFile() {
- $(this.$el).find('.file-content').syntaxHighlight();
+ syntaxHighlight($(this.$el).find('.file-content'));
},
},
mounted() {
diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js
index 120ce96f44d..af5dcf054ef 100644
--- a/app/assets/javascripts/repo/stores/actions.js
+++ b/app/assets/javascripts/repo/stores/actions.js
@@ -1,9 +1,10 @@
import Vue from 'vue';
+import { visitUrl } from '../../lib/utils/url_utility';
import flash from '../../flash';
import service from '../services';
import * as types from './mutation_types';
-export const redirectToUrl = (_, url) => gl.utils.visitUrl(url);
+export const redirectToUrl = (_, url) => visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js
index aa830e946a2..7c251e26bed 100644
--- a/app/assets/javascripts/repo/stores/actions/tree.js
+++ b/app/assets/javascripts/repo/stores/actions/tree.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../../../lib/utils/url_utility';
import { normalizeHeaders } from '../../../lib/utils/common_utils';
import flash from '../../../flash';
import service from '../../services';
@@ -73,7 +74,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => {
} else if (row.type === 'submodule') {
commit(types.TOGGLE_LOADING, row);
- gl.utils.visitUrl(row.url);
+ visitUrl(row.url);
} else if (row.type === 'blob' && row.opened) {
dispatch('setFileActive', row);
} else {
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index fa7f6825d7e..b830fcf7e80 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -3,226 +3,228 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
-(function() {
- this.Sidebar = (function() {
- function Sidebar(currentUser) {
- this.toggleTodo = this.toggleTodo.bind(this);
- this.sidebar = $('aside');
-
- this.removeListeners();
- this.addEventListeners();
+function Sidebar(currentUser) {
+ this.toggleTodo = this.toggleTodo.bind(this);
+ this.sidebar = $('aside');
+
+ this.removeListeners();
+ this.addEventListeners();
+}
+
+Sidebar.initialize = function(currentUser) {
+ if (!this.instance) {
+ this.instance = new Sidebar(currentUser);
+ }
+};
+
+Sidebar.prototype.removeListeners = function () {
+ this.sidebar.off('click', '.sidebar-collapsed-icon');
+ this.sidebar.off('hidden.gl.dropdown');
+ $('.dropdown').off('loading.gl.dropdown');
+ $('.dropdown').off('loaded.gl.dropdown');
+ $(document).off('click', '.js-sidebar-toggle');
+};
+
+Sidebar.prototype.addEventListeners = function() {
+ const $document = $(document);
+
+ this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
+ this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
+ $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
+ $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
+
+ $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');
+ $('.layout-page').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');
+ $('.layout-page').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);
+ ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
+ if ($this.attr('data-delete-path')) {
+ url = "" + ($this.attr('data-delete-path'));
+ } else {
+ url = "" + ($this.data('url'));
+ }
+
+ $this.tooltip('hide');
+
+ return $.ajax({
+ url: url,
+ type: ajaxType,
+ dataType: 'json',
+ data: {
+ issuable_id: $this.data('issuable-id'),
+ issuable_type: $this.data('issuable-type')
+ },
+ beforeSend: (function(_this) {
+ return function() {
+ $('.js-issuable-todo').disable()
+ .addClass('is-loading');
+ };
+ })(this)
+ }).done((function(_this) {
+ return function(data) {
+ return _this.todoUpdateDone(data);
+ };
+ })(this));
+};
+
+Sidebar.prototype.todoUpdateDone = function(data) {
+ const deletePath = data.delete_path ? data.delete_path : null;
+ const attrPrefix = deletePath ? 'mark' : 'todo';
+ const $todoBtns = $('.js-issuable-todo');
+
+ $(document).trigger('todo:toggle', data.count);
+
+ $todoBtns.each((i, el) => {
+ const $el = $(el);
+ const $elText = $el.find('.js-issuable-todo-inner');
+
+ $el.removeClass('is-loading')
+ .enable()
+ .attr('aria-label', $el.data(`${attrPrefix}-text`))
+ .attr('data-delete-path', deletePath)
+ .attr('title', $el.data(`${attrPrefix}-text`));
+
+ if ($el.hasClass('has-tooltip')) {
+ $el.tooltip('fixTitle');
}
- Sidebar.prototype.removeListeners = function () {
- this.sidebar.off('click', '.sidebar-collapsed-icon');
- this.sidebar.off('hidden.gl.dropdown');
- $('.dropdown').off('loading.gl.dropdown');
- $('.dropdown').off('loaded.gl.dropdown');
- $(document).off('click', '.js-sidebar-toggle');
- };
-
- Sidebar.prototype.addEventListeners = function() {
- const $document = $(document);
-
- this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
- this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
- $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
- $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
-
- $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);
- ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
- if ($this.attr('data-delete-path')) {
- url = "" + ($this.attr('data-delete-path'));
- } else {
- url = "" + ($this.data('url'));
- }
-
- $this.tooltip('hide');
-
- return $.ajax({
- url: url,
- type: ajaxType,
- dataType: 'json',
- data: {
- issuable_id: $this.data('issuable-id'),
- issuable_type: $this.data('issuable-type')
- },
- beforeSend: (function(_this) {
- return function() {
- $('.js-issuable-todo').disable()
- .addClass('is-loading');
- };
- })(this)
- }).done((function(_this) {
- return function(data) {
- return _this.todoUpdateDone(data);
- };
- })(this));
- };
-
- Sidebar.prototype.todoUpdateDone = function(data) {
- const deletePath = data.delete_path ? data.delete_path : null;
- const attrPrefix = deletePath ? 'mark' : 'todo';
- const $todoBtns = $('.js-issuable-todo');
-
- $(document).trigger('todo:toggle', data.count);
-
- $todoBtns.each((i, el) => {
- const $el = $(el);
- const $elText = $el.find('.js-issuable-todo-inner');
-
- $el.removeClass('is-loading')
- .enable()
- .attr('aria-label', $el.data(`${attrPrefix}-text`))
- .attr('data-delete-path', deletePath)
- .attr('title', $el.data(`${attrPrefix}-text`));
-
- if ($el.hasClass('has-tooltip')) {
- $el.tooltip('fixTitle');
- }
-
- if ($el.data(`${attrPrefix}-icon`)) {
- $elText.html($el.data(`${attrPrefix}-icon`));
- } else {
- $elText.text($el.data(`${attrPrefix}-text`));
- }
- });
- };
-
- Sidebar.prototype.sidebarDropdownLoading = function(e) {
- var $loading, $sidebarCollapsedIcon, i, img;
- $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
- img = $sidebarCollapsedIcon.find('img');
- i = $sidebarCollapsedIcon.find('i');
- $loading = $('<i class="fa fa-spinner fa-spin"></i>');
- if (img.length) {
- img.before($loading);
- return img.hide();
- } else if (i.length) {
- i.before($loading);
- return i.hide();
- }
- };
-
- Sidebar.prototype.sidebarDropdownLoaded = function(e) {
- var $sidebarCollapsedIcon, i, img;
- $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
- img = $sidebarCollapsedIcon.find('img');
- $sidebarCollapsedIcon.find('i.fa-spin').remove();
- i = $sidebarCollapsedIcon.find('i');
- if (img.length) {
- return img.show();
- } else {
- return i.show();
- }
- };
-
- Sidebar.prototype.sidebarCollapseClicked = function(e) {
- var $block, sidebar;
- if ($(e.currentTarget).hasClass('dont-change-state')) {
- return;
- }
- sidebar = e.data;
- e.preventDefault();
- $block = $(this).closest('.block');
- return sidebar.openDropdown($block);
- };
-
- Sidebar.prototype.openDropdown = function(blockOrName) {
- var $block;
- $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
- if (!this.isOpen()) {
- this.setCollapseAfterUpdate($block);
- this.toggleSidebar('open');
- }
-
- // Wait for the sidebar to trigger('click') open
- // so it doesn't cause our dropdown to close preemptively
- setTimeout(() => {
- $block.find('.js-sidebar-dropdown-toggle').trigger('click');
- });
- };
-
- Sidebar.prototype.setCollapseAfterUpdate = function($block) {
- $block.addClass('collapse-after-update');
- return $('.page-with-sidebar').addClass('with-overlay');
- };
-
- Sidebar.prototype.onSidebarDropdownHidden = function(e) {
- var $block, sidebar;
- sidebar = e.data;
- e.preventDefault();
- $block = $(e.target).closest('.block');
- return sidebar.sidebarDropdownHidden($block);
- };
-
- Sidebar.prototype.sidebarDropdownHidden = function($block) {
- if ($block.hasClass('collapse-after-update')) {
- $block.removeClass('collapse-after-update');
- $('.page-with-sidebar').removeClass('with-overlay');
- return this.toggleSidebar('hide');
- }
- };
-
- Sidebar.prototype.triggerOpenSidebar = function() {
- return this.sidebar.find('.js-sidebar-toggle').trigger('click');
- };
-
- Sidebar.prototype.toggleSidebar = function(action) {
- if (action == null) {
- action = 'toggle';
- }
- if (action === 'toggle') {
- this.triggerOpenSidebar();
- }
- if (action === 'open') {
- if (!this.isOpen()) {
- this.triggerOpenSidebar();
- }
- }
- if (action === 'hide') {
- if (this.isOpen()) {
- return this.triggerOpenSidebar();
- }
- }
- };
+ if ($el.data(`${attrPrefix}-icon`)) {
+ $elText.html($el.data(`${attrPrefix}-icon`));
+ } else {
+ $elText.text($el.data(`${attrPrefix}-text`));
+ }
+ });
+};
+
+Sidebar.prototype.sidebarDropdownLoading = function(e) {
+ var $loading, $sidebarCollapsedIcon, i, img;
+ $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+ img = $sidebarCollapsedIcon.find('img');
+ i = $sidebarCollapsedIcon.find('i');
+ $loading = $('<i class="fa fa-spinner fa-spin"></i>');
+ if (img.length) {
+ img.before($loading);
+ return img.hide();
+ } else if (i.length) {
+ i.before($loading);
+ return i.hide();
+ }
+};
+
+Sidebar.prototype.sidebarDropdownLoaded = function(e) {
+ var $sidebarCollapsedIcon, i, img;
+ $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+ img = $sidebarCollapsedIcon.find('img');
+ $sidebarCollapsedIcon.find('i.fa-spin').remove();
+ i = $sidebarCollapsedIcon.find('i');
+ if (img.length) {
+ return img.show();
+ } else {
+ return i.show();
+ }
+};
+
+Sidebar.prototype.sidebarCollapseClicked = function(e) {
+ var $block, sidebar;
+ if ($(e.currentTarget).hasClass('dont-change-state')) {
+ return;
+ }
+ sidebar = e.data;
+ e.preventDefault();
+ $block = $(this).closest('.block');
+ return sidebar.openDropdown($block);
+};
+
+Sidebar.prototype.openDropdown = function(blockOrName) {
+ var $block;
+ $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
+ if (!this.isOpen()) {
+ this.setCollapseAfterUpdate($block);
+ this.toggleSidebar('open');
+ }
+
+ // Wait for the sidebar to trigger('click') open
+ // so it doesn't cause our dropdown to close preemptively
+ setTimeout(() => {
+ $block.find('.js-sidebar-dropdown-toggle').trigger('click');
+ });
+};
+
+Sidebar.prototype.setCollapseAfterUpdate = function($block) {
+ $block.addClass('collapse-after-update');
+ return $('.layout-page').addClass('with-overlay');
+};
+
+Sidebar.prototype.onSidebarDropdownHidden = function(e) {
+ var $block, sidebar;
+ sidebar = e.data;
+ e.preventDefault();
+ $block = $(e.target).closest('.block');
+ return sidebar.sidebarDropdownHidden($block);
+};
+
+Sidebar.prototype.sidebarDropdownHidden = function($block) {
+ if ($block.hasClass('collapse-after-update')) {
+ $block.removeClass('collapse-after-update');
+ $('.layout-page').removeClass('with-overlay');
+ return this.toggleSidebar('hide');
+ }
+};
+
+Sidebar.prototype.triggerOpenSidebar = function() {
+ return this.sidebar.find('.js-sidebar-toggle').trigger('click');
+};
+
+Sidebar.prototype.toggleSidebar = function(action) {
+ if (action == null) {
+ action = 'toggle';
+ }
+ if (action === 'toggle') {
+ this.triggerOpenSidebar();
+ }
+ if (action === 'open') {
+ if (!this.isOpen()) {
+ this.triggerOpenSidebar();
+ }
+ }
+ if (action === 'hide') {
+ if (this.isOpen()) {
+ return this.triggerOpenSidebar();
+ }
+ }
+};
- Sidebar.prototype.isOpen = function() {
- return this.sidebar.is('.right-sidebar-expanded');
- };
+Sidebar.prototype.isOpen = function() {
+ return this.sidebar.is('.right-sidebar-expanded');
+};
- Sidebar.prototype.getBlock = function(name) {
- return this.sidebar.find(".block." + name);
- };
+Sidebar.prototype.getBlock = function(name) {
+ return this.sidebar.find(".block." + name);
+};
- return Sidebar;
- })();
-}).call(window);
+export default Sidebar;
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 07fee53d814..363322af47a 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -1,118 +1,113 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
import Flash from './flash';
import Api from './api';
-(function() {
- this.Search = (function() {
- function Search() {
- var $groupDropdown, $projectDropdown;
- $groupDropdown = $('.js-search-group-dropdown');
- $projectDropdown = $('.js-search-project-dropdown');
- this.groupId = $groupDropdown.data('group-id');
- this.eventListeners();
- $groupDropdown.glDropdown({
- selectable: true,
- filterable: true,
- fieldName: 'group_id',
- search: {
- fields: ['full_name']
- },
- data: function(term, callback) {
- return Api.groups(term, {}, function(data) {
+export default class Search {
+ constructor() {
+ const $groupDropdown = $('.js-search-group-dropdown');
+ const $projectDropdown = $('.js-search-project-dropdown');
+
+ this.searchInput = '.js-search-input';
+ this.searchClear = '.js-search-clear';
+
+ this.groupId = $groupDropdown.data('group-id');
+ this.eventListeners();
+
+ $groupDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'group_id',
+ search: {
+ fields: ['full_name'],
+ },
+ data(term, callback) {
+ return Api.groups(term, {}, (data) => {
+ data.unshift({
+ full_name: 'Any',
+ });
+ data.splice(1, 0, 'divider');
+ return callback(data);
+ });
+ },
+ id(obj) {
+ return obj.id;
+ },
+ text(obj) {
+ return obj.full_name;
+ },
+ toggleLabel(obj) {
+ return `${($groupDropdown.data('default-label'))} ${obj.full_name}`;
+ },
+ clicked: () => Search.submitSearch(),
+ });
+
+ $projectDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'project_id',
+ search: {
+ fields: ['name'],
+ },
+ data: (term, callback) => {
+ this.getProjectsData(term)
+ .then((data) => {
data.unshift({
- full_name: 'Any'
+ name_with_namespace: 'Any',
});
data.splice(1, 0, 'divider');
- return callback(data);
- });
- },
- id: function(obj) {
- return obj.id;
- },
- text: function(obj) {
- return obj.full_name;
- },
- toggleLabel: function(obj) {
- return ($groupDropdown.data('default-label')) + " " + obj.full_name;
- },
- clicked: (function(_this) {
- return function() {
- return _this.submitSearch();
- };
- })(this)
- });
- $projectDropdown.glDropdown({
- selectable: true,
- filterable: true,
- fieldName: 'project_id',
- search: {
- fields: ['name']
- },
- data: (term, callback) => {
- this.getProjectsData(term)
- .then((data) => {
- data.unshift({
- name_with_namespace: 'Any'
- });
- data.splice(1, 0, 'divider');
- return data;
- })
- .then(data => callback(data))
- .catch(() => new Flash('Error fetching projects'));
- },
- id: function(obj) {
- return obj.id;
- },
- text: function(obj) {
- return obj.name_with_namespace;
- },
- toggleLabel: function(obj) {
- return ($projectDropdown.data('default-label')) + " " + obj.name_with_namespace;
- },
- clicked: (function(_this) {
- return function() {
- return _this.submitSearch();
- };
- })(this)
- });
- }
+ return data;
+ })
+ .then(data => callback(data))
+ .catch(() => new Flash('Error fetching projects'));
+ },
+ id(obj) {
+ return obj.id;
+ },
+ text(obj) {
+ return obj.name_with_namespace;
+ },
+ toggleLabel(obj) {
+ return `${($projectDropdown.data('default-label'))} ${obj.name_with_namespace}`;
+ },
+ clicked: () => Search.submitSearch(),
+ });
+ }
- Search.prototype.eventListeners = function() {
- $(document).off('keyup', '.js-search-input').on('keyup', '.js-search-input', this.searchKeyUp);
- return $(document).off('click', '.js-search-clear').on('click', '.js-search-clear', this.clearSearchField);
- };
+ eventListeners() {
+ $(document)
+ .off('keyup', this.searchInput)
+ .on('keyup', this.searchInput, this.searchKeyUp);
+ $(document)
+ .off('click', this.searchClear)
+ .on('click', this.searchClear, this.clearSearchField.bind(this));
+ }
- Search.prototype.submitSearch = function() {
- return $('.js-search-form').submit();
- };
+ static submitSearch() {
+ return $('.js-search-form').submit();
+ }
- Search.prototype.searchKeyUp = function() {
- var $input;
- $input = $(this);
- if ($input.val() === '') {
- return $('.js-search-clear').addClass('hidden');
- } else {
- return $('.js-search-clear').removeClass('hidden');
- }
- };
-
- Search.prototype.clearSearchField = function() {
- return $('.js-search-input').val('').trigger('keyup').focus();
- };
+ searchKeyUp() {
+ const $input = $(this);
+ if ($input.val() === '') {
+ $('.js-search-clear').addClass('hidden');
+ } else {
+ $('.js-search-clear').removeClass('hidden');
+ }
+ }
- Search.prototype.getProjectsData = function(term) {
- return new Promise((resolve) => {
- if (this.groupId) {
- Api.groupProjects(this.groupId, term, resolve);
- } else {
- Api.projects(term, {
- order_by: 'id',
- }, resolve);
- }
- });
- };
+ clearSearchField() {
+ return $(this.searchInput).val('').trigger('keyup').focus();
+ }
- return Search;
- })();
-}).call(window);
+ getProjectsData(term) {
+ return new Promise((resolve) => {
+ if (this.groupId) {
+ Api.groupProjects(this.groupId, term, resolve);
+ } else {
+ Api.projects(term, {
+ order_by: 'id',
+ }, resolve);
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index e40a3596200..98b524f7e3f 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -8,448 +8,445 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
* When the user clicks `x` button it cleans the input and closes the dropdown.
*/
-((global) => {
- const KEYCODE = {
- ESCAPE: 27,
- BACKSPACE: 8,
- ENTER: 13,
- UP: 38,
- DOWN: 40,
- };
-
- class SearchAutocomplete {
- constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
- this.bindEventContext();
- this.wrap = wrap || $('.search');
- this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
- this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
- this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
- this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
- this.dropdown = this.wrap.find('.dropdown');
- this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
- this.dropdownContent = this.dropdown.find('.dropdown-content');
- this.locationBadgeEl = this.getElement('.location-badge');
- this.scopeInputEl = this.getElement('#scope');
- this.searchInput = this.getElement('.search-input');
- this.projectInputEl = this.getElement('#search_project_id');
- this.groupInputEl = this.getElement('#group_id');
- this.searchCodeInputEl = this.getElement('#search_code');
- this.repositoryInputEl = this.getElement('#repository_ref');
- this.clearInput = this.getElement('.js-clear-input');
- this.saveOriginalState();
-
- // Only when user is logged in
- if (gon.current_user_id) {
- this.createAutocomplete();
- }
+const KEYCODE = {
+ ESCAPE: 27,
+ BACKSPACE: 8,
+ ENTER: 13,
+ UP: 38,
+ DOWN: 40,
+};
+
+function setSearchOptions() {
+ var $projectOptionsDataEl = $('.js-search-project-options');
+ var $groupOptionsDataEl = $('.js-search-group-options');
+ var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
+
+ if ($projectOptionsDataEl.length) {
+ gl.projectOptions = gl.projectOptions || {};
+
+ var projectPath = $projectOptionsDataEl.data('project-path');
+
+ gl.projectOptions[projectPath] = {
+ name: $projectOptionsDataEl.data('name'),
+ issuesPath: $projectOptionsDataEl.data('issues-path'),
+ issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
+ mrPath: $projectOptionsDataEl.data('mr-path'),
+ };
+ }
- this.searchInput.addClass('disabled');
- this.saveTextLength();
- this.bindEvents();
- this.dropdownToggle.dropdown();
- }
+ if ($groupOptionsDataEl.length) {
+ gl.groupOptions = gl.groupOptions || {};
- // Finds an element inside wrapper element
- bindEventContext() {
- this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
- this.onClearInputClick = this.onClearInputClick.bind(this);
- this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
- this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
- this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
- }
- getElement(selector) {
- return this.wrap.find(selector);
- }
+ var groupPath = $groupOptionsDataEl.data('group-path');
- saveOriginalState() {
- return this.originalState = this.serializeState();
- }
+ gl.groupOptions[groupPath] = {
+ name: $groupOptionsDataEl.data('name'),
+ issuesPath: $groupOptionsDataEl.data('issues-path'),
+ mrPath: $groupOptionsDataEl.data('mr-path'),
+ };
+ }
- saveTextLength() {
- return this.lastTextLength = this.searchInput.val().length;
+ if ($dashboardOptionsDataEl.length) {
+ gl.dashboardOptions = {
+ issuesPath: $dashboardOptionsDataEl.data('issues-path'),
+ mrPath: $dashboardOptionsDataEl.data('mr-path'),
+ };
+ }
+}
+
+export default class SearchAutocomplete {
+ constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
+ setSearchOptions();
+ this.bindEventContext();
+ this.wrap = wrap || $('.search');
+ this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
+ this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
+ this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
+ this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
+ this.dropdown = this.wrap.find('.dropdown');
+ this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
+ this.dropdownContent = this.dropdown.find('.dropdown-content');
+ this.locationBadgeEl = this.getElement('.location-badge');
+ this.scopeInputEl = this.getElement('#scope');
+ this.searchInput = this.getElement('.search-input');
+ this.projectInputEl = this.getElement('#search_project_id');
+ this.groupInputEl = this.getElement('#group_id');
+ this.searchCodeInputEl = this.getElement('#search_code');
+ this.repositoryInputEl = this.getElement('#repository_ref');
+ this.clearInput = this.getElement('.js-clear-input');
+ this.saveOriginalState();
+
+ // Only when user is logged in
+ if (gon.current_user_id) {
+ this.createAutocomplete();
}
- createAutocomplete() {
- return this.searchInput.glDropdown({
- filterInputBlur: false,
- filterable: true,
- filterRemote: true,
- highlight: true,
- enterCallback: false,
- filterInput: 'input#search',
- search: {
- fields: ['text'],
- },
- id: this.getSearchText,
- data: this.getData.bind(this),
- selectable: true,
- clicked: this.onClick.bind(this),
- });
+ this.searchInput.addClass('disabled');
+ this.saveTextLength();
+ this.bindEvents();
+ this.dropdownToggle.dropdown();
+ }
+
+ // Finds an element inside wrapper element
+ bindEventContext() {
+ this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
+ this.onClearInputClick = this.onClearInputClick.bind(this);
+ this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
+ this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
+ this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
+ }
+ getElement(selector) {
+ return this.wrap.find(selector);
+ }
+
+ saveOriginalState() {
+ return this.originalState = this.serializeState();
+ }
+
+ saveTextLength() {
+ return this.lastTextLength = this.searchInput.val().length;
+ }
+
+ createAutocomplete() {
+ return this.searchInput.glDropdown({
+ filterInputBlur: false,
+ filterable: true,
+ filterRemote: true,
+ highlight: true,
+ enterCallback: false,
+ filterInput: 'input#search',
+ search: {
+ fields: ['text'],
+ },
+ id: this.getSearchText,
+ data: this.getData.bind(this),
+ selectable: true,
+ clicked: this.onClick.bind(this),
+ });
+ }
+
+ getSearchText(selectedObject, el) {
+ return selectedObject.id ? selectedObject.text : '';
+ }
+
+ getData(term, callback) {
+ if (!term) {
+ const contents = this.getCategoryContents();
+ if (contents) {
+ this.searchInput.data('glDropdown').filter.options.callback(contents);
+ this.enableAutocomplete();
+ }
+ return;
}
- getSearchText(selectedObject, el) {
- return selectedObject.id ? selectedObject.text : '';
+ // Prevent multiple ajax calls
+ if (this.loadingSuggestions) {
+ return;
}
- getData(term, callback) {
- if (!term) {
- const contents = this.getCategoryContents();
- if (contents) {
- this.searchInput.data('glDropdown').filter.options.callback(contents);
- this.enableAutocomplete();
- }
- return;
- }
+ this.loadingSuggestions = true;
- // Prevent multiple ajax calls
- if (this.loadingSuggestions) {
+ return $.get(this.autocompletePath, {
+ project_id: this.projectId,
+ project_ref: this.projectRef,
+ term: term,
+ }, (response) => {
+ var firstCategory, i, lastCategory, len, suggestion;
+ // Hide dropdown menu if no suggestions returns
+ if (!response.length) {
+ this.disableAutocomplete();
return;
}
- this.loadingSuggestions = true;
-
- return $.get(this.autocompletePath, {
- project_id: this.projectId,
- project_ref: this.projectRef,
- term: term,
- }, (response) => {
- var firstCategory, i, lastCategory, len, suggestion;
- // Hide dropdown menu if no suggestions returns
- if (!response.length) {
- this.disableAutocomplete();
- return;
- }
-
- const data = [];
- // List results
- firstCategory = true;
- for (i = 0, len = response.length; i < len; i += 1) {
- suggestion = response[i];
- // Add group header before list each group
- if (lastCategory !== suggestion.category) {
- if (!firstCategory) {
- data.push('separator');
- }
- if (firstCategory) {
- firstCategory = false;
- }
- data.push({
- header: suggestion.category,
- });
- lastCategory = suggestion.category;
+ const data = [];
+ // List results
+ firstCategory = true;
+ for (i = 0, len = response.length; i < len; i += 1) {
+ suggestion = response[i];
+ // Add group header before list each group
+ if (lastCategory !== suggestion.category) {
+ if (!firstCategory) {
+ data.push('separator');
+ }
+ if (firstCategory) {
+ firstCategory = false;
}
data.push({
- id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
- category: suggestion.category,
- text: suggestion.label,
- url: suggestion.url,
- });
- }
- // Add option to proceed with the search
- if (data.length) {
- data.push('separator');
- data.push({
- text: "Result name contains \"" + term + "\"",
- url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
+ header: suggestion.category,
});
+ lastCategory = suggestion.category;
}
- return callback(data);
- })
- .always(() => { this.loadingSuggestions = false; });
- }
-
- getCategoryContents() {
- const userId = gon.current_user_id;
- const userName = gon.current_username;
- const { projectOptions, groupOptions, dashboardOptions } = gl;
-
- // Get options
- let options;
- if (isInGroupsPage() && groupOptions) {
- options = groupOptions[getGroupSlug()];
- } else if (isInProjectPage() && projectOptions) {
- options = projectOptions[getProjectSlug()];
- } else if (dashboardOptions) {
- options = dashboardOptions;
+ data.push({
+ id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
+ category: suggestion.category,
+ text: suggestion.label,
+ url: suggestion.url,
+ });
}
-
- const { issuesPath, mrPath, name, issuesDisabled } = options;
- const baseItems = [];
-
- if (name) {
- baseItems.push({
- header: `${name}`,
+ // Add option to proceed with the search
+ if (data.length) {
+ data.push('separator');
+ data.push({
+ text: "Result name contains \"" + term + "\"",
+ url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
});
}
+ return callback(data);
+ })
+ .always(() => { this.loadingSuggestions = false; });
+ }
- const issueItems = [
- {
- text: 'Issues assigned to me',
- url: `${issuesPath}/?assignee_username=${userName}`,
- },
- {
- text: "Issues I've created",
- url: `${issuesPath}/?author_username=${userName}`,
- },
- ];
- const mergeRequestItems = [
- {
- text: 'Merge requests assigned to me',
- url: `${mrPath}/?assignee_username=${userName}`,
- },
- {
- text: "Merge requests I've created",
- url: `${mrPath}/?author_username=${userName}`,
- },
- ];
-
- let items;
- if (issuesDisabled) {
- items = baseItems.concat(mergeRequestItems);
- } else {
- items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
- }
- return items;
+ getCategoryContents() {
+ const userId = gon.current_user_id;
+ const userName = gon.current_username;
+ const { projectOptions, groupOptions, dashboardOptions } = gl;
+
+ // Get options
+ let options;
+ if (isInGroupsPage() && groupOptions) {
+ options = groupOptions[getGroupSlug()];
+ } else if (isInProjectPage() && projectOptions) {
+ options = projectOptions[getProjectSlug()];
+ } else if (dashboardOptions) {
+ options = dashboardOptions;
}
- serializeState() {
- return {
- // Search Criteria
- search_project_id: this.projectInputEl.val(),
- group_id: this.groupInputEl.val(),
- search_code: this.searchCodeInputEl.val(),
- repository_ref: this.repositoryInputEl.val(),
- scope: this.scopeInputEl.val(),
- // Location badge
- _location: this.locationBadgeEl.text(),
- };
+ const { issuesPath, mrPath, name, issuesDisabled } = options;
+ const baseItems = [];
+
+ if (name) {
+ baseItems.push({
+ header: `${name}`,
+ });
}
- bindEvents() {
- this.searchInput.on('keydown', this.onSearchInputKeyDown);
- this.searchInput.on('keyup', this.onSearchInputKeyUp);
- this.searchInput.on('focus', this.onSearchInputFocus);
- this.searchInput.on('blur', this.onSearchInputBlur);
- this.clearInput.on('click', this.onClearInputClick);
- this.locationBadgeEl.on('click', () => this.searchInput.focus());
+ const issueItems = [
+ {
+ text: 'Issues assigned to me',
+ url: `${issuesPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: "Issues I've created",
+ url: `${issuesPath}/?author_username=${userName}`,
+ },
+ ];
+ const mergeRequestItems = [
+ {
+ text: 'Merge requests assigned to me',
+ url: `${mrPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: "Merge requests I've created",
+ url: `${mrPath}/?author_username=${userName}`,
+ },
+ ];
+
+ let items;
+ if (issuesDisabled) {
+ items = baseItems.concat(mergeRequestItems);
+ } else {
+ items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
}
+ return items;
+ }
- enableAutocomplete() {
- // No need to enable anything if user is not logged in
- if (!gon.current_user_id) {
- return;
- }
+ serializeState() {
+ return {
+ // Search Criteria
+ search_project_id: this.projectInputEl.val(),
+ group_id: this.groupInputEl.val(),
+ search_code: this.searchCodeInputEl.val(),
+ repository_ref: this.repositoryInputEl.val(),
+ scope: this.scopeInputEl.val(),
+ // Location badge
+ _location: this.locationBadgeEl.text(),
+ };
+ }
- // If the dropdown is closed, we'll open it
- if (!this.dropdown.hasClass('open')) {
- this.loadingSuggestions = false;
- this.dropdownToggle.dropdown('toggle');
- return this.searchInput.removeClass('disabled');
- }
+ bindEvents() {
+ this.searchInput.on('keydown', this.onSearchInputKeyDown);
+ this.searchInput.on('keyup', this.onSearchInputKeyUp);
+ this.searchInput.on('focus', this.onSearchInputFocus);
+ this.searchInput.on('blur', this.onSearchInputBlur);
+ this.clearInput.on('click', this.onClearInputClick);
+ this.locationBadgeEl.on('click', () => this.searchInput.focus());
+ }
+
+ enableAutocomplete() {
+ // No need to enable anything if user is not logged in
+ if (!gon.current_user_id) {
+ return;
}
- // Saves last length of the entered text
- onSearchInputKeyDown() {
- return this.saveTextLength();
+ // If the dropdown is closed, we'll open it
+ if (!this.dropdown.hasClass('open')) {
+ this.loadingSuggestions = false;
+ this.dropdownToggle.dropdown('toggle');
+ return this.searchInput.removeClass('disabled');
}
+ }
- onSearchInputKeyUp(e) {
- switch (e.keyCode) {
- case KEYCODE.BACKSPACE:
- // when trying to remove the location badge
- if (this.lastTextLength === 0 && this.badgePresent()) {
- this.removeLocationBadge();
- }
- // When removing the last character and no badge is present
- if (this.lastTextLength === 1) {
- this.disableAutocomplete();
- }
- // When removing any character from existin value
- if (this.lastTextLength > 1) {
- this.enableAutocomplete();
- }
- break;
- case KEYCODE.ESCAPE:
- this.restoreOriginalState();
- break;
- case KEYCODE.ENTER:
+ // Saves last length of the entered text
+ onSearchInputKeyDown() {
+ return this.saveTextLength();
+ }
+
+ onSearchInputKeyUp(e) {
+ switch (e.keyCode) {
+ case KEYCODE.BACKSPACE:
+ // when trying to remove the location badge
+ if (this.lastTextLength === 0 && this.badgePresent()) {
+ this.removeLocationBadge();
+ }
+ // When removing the last character and no badge is present
+ if (this.lastTextLength === 1) {
+ this.disableAutocomplete();
+ }
+ // When removing any character from existin value
+ if (this.lastTextLength > 1) {
+ this.enableAutocomplete();
+ }
+ break;
+ case KEYCODE.ESCAPE:
+ this.restoreOriginalState();
+ break;
+ case KEYCODE.ENTER:
+ this.disableAutocomplete();
+ break;
+ case KEYCODE.UP:
+ case KEYCODE.DOWN:
+ return;
+ default:
+ // Handle the case when deleting the input value other than backspace
+ // e.g. Pressing ctrl + backspace or ctrl + x
+ if (this.searchInput.val() === '') {
this.disableAutocomplete();
- break;
- case KEYCODE.UP:
- case KEYCODE.DOWN:
- return;
- default:
- // Handle the case when deleting the input value other than backspace
- // e.g. Pressing ctrl + backspace or ctrl + x
- if (this.searchInput.val() === '') {
- this.disableAutocomplete();
- } else {
- // We should display the menu only when input is not empty
- if (e.keyCode !== KEYCODE.ENTER) {
- this.enableAutocomplete();
- }
+ } else {
+ // We should display the menu only when input is not empty
+ if (e.keyCode !== KEYCODE.ENTER) {
+ this.enableAutocomplete();
}
- }
- this.wrap.toggleClass('has-value', !!e.target.value);
+ }
}
+ this.wrap.toggleClass('has-value', !!e.target.value);
+ }
- onSearchInputFocus() {
- this.isFocused = true;
- this.wrap.addClass('search-active');
- if (this.getValue() === '') {
- return this.getData();
- }
+ onSearchInputFocus() {
+ this.isFocused = true;
+ this.wrap.addClass('search-active');
+ if (this.getValue() === '') {
+ return this.getData();
}
+ }
- getValue() {
- return this.searchInput.val();
- }
+ getValue() {
+ return this.searchInput.val();
+ }
- onClearInputClick(e) {
- e.preventDefault();
- this.wrap.toggleClass('has-value', !!e.target.value);
- return this.searchInput.val('').focus();
- }
+ onClearInputClick(e) {
+ e.preventDefault();
+ this.wrap.toggleClass('has-value', !!e.target.value);
+ return this.searchInput.val('').focus();
+ }
- onSearchInputBlur(e) {
- this.isFocused = false;
- this.wrap.removeClass('search-active');
- // If input is blank then restore state
- if (this.searchInput.val() === '') {
- return this.restoreOriginalState();
- }
+ onSearchInputBlur(e) {
+ this.isFocused = false;
+ this.wrap.removeClass('search-active');
+ // If input is blank then restore state
+ if (this.searchInput.val() === '') {
+ return this.restoreOriginalState();
}
+ }
- addLocationBadge(item) {
- var badgeText, category, value;
- category = item.category != null ? item.category + ": " : '';
- value = item.value != null ? item.value : '';
- badgeText = "" + category + value;
- this.locationBadgeEl.text(badgeText).show();
- return this.wrap.addClass('has-location-badge');
- }
+ addLocationBadge(item) {
+ var badgeText, category, value;
+ category = item.category != null ? item.category + ": " : '';
+ value = item.value != null ? item.value : '';
+ badgeText = "" + category + value;
+ this.locationBadgeEl.text(badgeText).show();
+ return this.wrap.addClass('has-location-badge');
+ }
- hasLocationBadge() {
- return this.wrap.is('.has-location-badge');
- }
+ hasLocationBadge() {
+ return this.wrap.is('.has-location-badge');
+ }
- restoreOriginalState() {
- var i, input, inputs, len;
- inputs = Object.keys(this.originalState);
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
- this.getElement("#" + input).val(this.originalState[input]);
- }
- if (this.originalState._location === '') {
- return this.locationBadgeEl.hide();
- } else {
- return this.addLocationBadge({
- value: this.originalState._location,
- });
- }
+ restoreOriginalState() {
+ var i, input, inputs, len;
+ inputs = Object.keys(this.originalState);
+ for (i = 0, len = inputs.length; i < len; i += 1) {
+ input = inputs[i];
+ this.getElement("#" + input).val(this.originalState[input]);
}
-
- badgePresent() {
- return this.locationBadgeEl.length;
+ if (this.originalState._location === '') {
+ return this.locationBadgeEl.hide();
+ } else {
+ return this.addLocationBadge({
+ value: this.originalState._location,
+ });
}
+ }
- resetSearchState() {
- var i, input, inputs, len, results;
- inputs = Object.keys(this.originalState);
- results = [];
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
- // _location isnt a input
- if (input === '_location') {
- break;
- }
- results.push(this.getElement("#" + input).val(''));
+ badgePresent() {
+ return this.locationBadgeEl.length;
+ }
+
+ resetSearchState() {
+ var i, input, inputs, len, results;
+ inputs = Object.keys(this.originalState);
+ results = [];
+ for (i = 0, len = inputs.length; i < len; i += 1) {
+ input = inputs[i];
+ // _location isnt a input
+ if (input === '_location') {
+ break;
}
- return results;
+ results.push(this.getElement("#" + input).val(''));
}
+ return results;
+ }
- removeLocationBadge() {
- this.locationBadgeEl.hide();
- this.resetSearchState();
- this.wrap.removeClass('has-location-badge');
- return this.disableAutocomplete();
- }
+ removeLocationBadge() {
+ this.locationBadgeEl.hide();
+ this.resetSearchState();
+ this.wrap.removeClass('has-location-badge');
+ return this.disableAutocomplete();
+ }
- disableAutocomplete() {
- if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
- this.searchInput.addClass('disabled');
- this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
- this.restoreMenu();
- }
+ disableAutocomplete() {
+ if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
+ this.searchInput.addClass('disabled');
+ this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
+ this.restoreMenu();
}
+ }
- restoreMenu() {
- var html;
- html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
- return this.dropdownContent.html(html);
- }
+ restoreMenu() {
+ var html;
+ html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
+ return this.dropdownContent.html(html);
+ }
- onClick(item, $el, e) {
- if (location.pathname.indexOf(item.url) !== -1) {
- if (!e.metaKey) e.preventDefault();
- if (!this.badgePresent) {
- if (item.category === 'Projects') {
- this.projectInputEl.val(item.id);
- this.addLocationBadge({
- value: 'This project',
- });
- }
- if (item.category === 'Groups') {
- this.groupInputEl.val(item.id);
- this.addLocationBadge({
- value: 'This group',
- });
- }
+ onClick(item, $el, e) {
+ if (location.pathname.indexOf(item.url) !== -1) {
+ if (!e.metaKey) e.preventDefault();
+ if (!this.badgePresent) {
+ if (item.category === 'Projects') {
+ this.projectInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This project',
+ });
+ }
+ if (item.category === 'Groups') {
+ this.groupInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This group',
+ });
}
- $el.removeClass('is-active');
- this.disableAutocomplete();
- return this.searchInput.val('').focus();
}
+ $el.removeClass('is-active');
+ this.disableAutocomplete();
+ return this.searchInput.val('').focus();
}
}
-
- global.SearchAutocomplete = SearchAutocomplete;
-
- $(function() {
- var $projectOptionsDataEl = $('.js-search-project-options');
- var $groupOptionsDataEl = $('.js-search-group-options');
- var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
-
- if ($projectOptionsDataEl.length) {
- gl.projectOptions = gl.projectOptions || {};
-
- var projectPath = $projectOptionsDataEl.data('project-path');
-
- gl.projectOptions[projectPath] = {
- name: $projectOptionsDataEl.data('name'),
- issuesPath: $projectOptionsDataEl.data('issues-path'),
- issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
- mrPath: $projectOptionsDataEl.data('mr-path'),
- };
- }
-
- if ($groupOptionsDataEl.length) {
- gl.groupOptions = gl.groupOptions || {};
-
- var groupPath = $groupOptionsDataEl.data('group-path');
-
- gl.groupOptions[groupPath] = {
- name: $groupOptionsDataEl.data('name'),
- issuesPath: $groupOptionsDataEl.data('issues-path'),
- mrPath: $groupOptionsDataEl.data('mr-path'),
- };
- }
-
- if ($dashboardOptionsDataEl.length) {
- gl.dashboardOptions = {
- issuesPath: $dashboardOptionsDataEl.data('issues-path'),
- mrPath: $dashboardOptionsDataEl.data('mr-path'),
- };
- }
- });
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index ebe7a99ffae..130730b1700 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,5 +1,6 @@
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
+import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation';
const defaultStopCallback = Mousetrap.stopCallback;
@@ -38,7 +39,7 @@ export default class Shortcuts {
if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
Mousetrap.bind('t', () => {
- gl.utils.visitUrl(findFileURL);
+ visitUrl(findFileURL);
});
}
@@ -62,7 +63,7 @@ export default class Shortcuts {
} else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
- gl.utils.refreshCurrentPage();
+ refreshCurrentPage();
}
static toggleMarkdownPreview(e) {
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
index fbc57bb4304..cf309be4f6f 100644
--- a/app/assets/javascripts/shortcuts_blob.js
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -1,5 +1,5 @@
/* global Mousetrap */
-
+import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import Shortcuts from './shortcuts';
const defaults = {
@@ -18,9 +18,9 @@ export default class ShortcutsBlob extends Shortcuts {
moveToFilePermalink() {
if (this.options.fileBlobPermalinkUrl) {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const hashUrlString = hash ? `#${hash}` : '';
- gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
+ visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
}
}
}
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 4f4f606d293..292e3d6a657 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,8 +1,8 @@
/* global Mousetrap */
-/* global sidebar */
import _ from 'underscore';
import 'mousetrap';
+import Sidebar from './right_sidebar';
import ShortcutsNavigation from './shortcuts_navigation';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
- this.editBtn = document.querySelector('.issuable-edit');
+ this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
@@ -69,7 +69,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
}
static openSidebarDropdown(name) {
- sidebar.openDropdown(name);
+ Sidebar.instance.openDropdown(name);
return false;
}
}
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
index 74c17bc14a2..9e47039d920 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
@@ -1,22 +1,32 @@
import Flash from '../../../flash';
import AssigneeTitle from './assignee_title';
import Assignees from './assignees';
-
import Store from '../../stores/sidebar_store';
-import Mediator from '../../sidebar_mediator';
-
import eventHub from '../../event_hub';
export default {
name: 'SidebarAssignees',
data() {
return {
- mediator: new Mediator(),
store: new Store(),
loading: false,
- field: '',
};
},
+ props: {
+ mediator: {
+ type: Object,
+ required: true,
+ },
+ field: {
+ type: String,
+ required: true,
+ },
+ signedIn: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
components: {
'assignee-title': AssigneeTitle,
assignees: Assignees,
@@ -61,10 +71,6 @@ export default {
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
},
- beforeMount() {
- this.field = this.$el.dataset.field;
- this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
- },
template: `
<div>
<assignee-title
diff --git a/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue b/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
index c1296b28db7..6fcd2f95309 100644
--- a/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/sidebar_participants.vue
@@ -1,15 +1,19 @@
<script>
import Store from '../../stores/sidebar_store';
-import Mediator from '../../sidebar_mediator';
import participants from './participants.vue';
export default {
data() {
return {
- mediator: new Mediator(),
store: new Store(),
};
},
+ props: {
+ mediator: {
+ type: Object,
+ required: true,
+ },
+ },
components: {
participants,
},
@@ -21,6 +25,7 @@ export default {
<participants
:loading="store.isFetching.participants"
:participants="store.participants"
- :number-of-less-participants="7" />
+ :number-of-less-participants="7"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
index 25acc099699..f4bae1d3dd5 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
@@ -1,6 +1,5 @@
<script>
import Store from '../../stores/sidebar_store';
-import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub';
import Flash from '../../../flash';
import { __ } from '../../../locale';
@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue';
export default {
data() {
return {
- mediator: new Mediator(),
store: new Store(),
};
},
-
+ props: {
+ mediator: {
+ type: Object,
+ required: true,
+ },
+ },
components: {
subscriptions,
},
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 4032f156b15..56cc78ca0ca 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate);
+function mountAssigneesComponent(mediator) {
+ const el = document.getElementById('js-vue-sidebar-assignees');
+
+ if (!el) return;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ components: {
+ SidebarAssignees,
+ },
+ render: createElement => createElement('sidebar-assignees', {
+ props: {
+ mediator,
+ field: el.dataset.field,
+ signedIn: el.hasAttribute('data-signed-in'),
+ },
+ }),
+ });
+}
+
function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point');
@@ -49,9 +70,10 @@ function mountLockComponent(mediator) {
}).$mount(el);
}
-function mountParticipantsComponent() {
+function mountParticipantsComponent(mediator) {
const el = document.querySelector('.js-sidebar-participants-entry-point');
+ // eslint-disable-next-line no-new
if (!el) return;
// eslint-disable-next-line no-new
@@ -60,11 +82,15 @@ function mountParticipantsComponent() {
components: {
sidebarParticipants,
},
- render: createElement => createElement('sidebar-participants', {}),
+ render: createElement => createElement('sidebar-participants', {
+ props: {
+ mediator,
+ },
+ }),
});
}
-function mountSubscriptionsComponent() {
+function mountSubscriptionsComponent(mediator) {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return;
@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() {
components: {
sidebarSubscriptions,
},
- render: createElement => createElement('sidebar-subscriptions', {}),
+ render: createElement => createElement('sidebar-subscriptions', {
+ props: {
+ mediator,
+ },
+ }),
});
}
-function mount(mediator) {
- 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);
- }
+function mountTimeTrackingComponent() {
+ const el = document.getElementById('issuable-time-tracker');
+ if (!el) return;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ components: {
+ SidebarTimeTracking,
+ },
+ render: createElement => createElement('sidebar-time-tracking', {}),
+ });
+}
+
+export function mountSidebar(mediator) {
+ mountAssigneesComponent(mediator);
mountConfidentialComponent(mediator);
mountLockComponent(mediator);
- mountParticipantsComponent();
- mountSubscriptionsComponent();
+ mountParticipantsComponent(mediator);
+ mountSubscriptionsComponent(mediator);
new SidebarMoveIssue(
mediator,
@@ -98,7 +137,9 @@ function mount(mediator) {
$('.js-move-issue-confirmation-button'),
).init();
- new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
+ mountTimeTrackingComponent();
}
-export default mount;
+export function getSidebarOptions() {
+ return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
+}
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index f78287e504b..04c39d7b6b5 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -1,9 +1,8 @@
import Mediator from './sidebar_mediator';
-import mountSidebar from './mount_sidebar';
+import { mountSidebar, getSidebarOptions } from './mount_sidebar';
function domContentLoaded() {
- const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
- const mediator = new Mediator(sidebarOptions);
+ const mediator = new Mediator(getSidebarOptions());
mediator.fetch();
mountSidebar(mediator);
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index d4c07a188b3..d86557e870a 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import Service from './services/sidebar_service';
import Store from './stores/sidebar_store';
@@ -7,7 +8,6 @@ export default class SidebarMediator {
if (!SidebarMediator.singleton) {
this.initSingleton(options);
}
-
return SidebarMediator.singleton;
}
@@ -81,7 +81,7 @@ export default class SidebarMediator {
.then(response => response.json())
.then((data) => {
if (location.pathname !== data.web_url) {
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
}
});
}
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 73eb25e2333..f20cc6d8cca 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -1,33 +1,37 @@
export default class SidebarStore {
- constructor(store) {
+ constructor(options) {
if (!SidebarStore.singleton) {
- const { currentUser, rootPath, editable } = store;
- this.currentUser = currentUser;
- this.rootPath = rootPath;
- this.editable = editable;
- this.timeEstimate = 0;
- this.totalTimeSpent = 0;
- this.humanTimeEstimate = '';
- this.humanTimeSpent = '';
- this.assignees = [];
- this.isFetching = {
- assignees: true,
- participants: true,
- subscriptions: true,
- };
- this.isLoading = {};
- this.autocompleteProjects = [];
- this.moveToProjectId = 0;
- this.isLockDialogOpen = false;
- this.participants = [];
- this.subscribed = null;
-
- SidebarStore.singleton = this;
+ this.initSingleton(options);
}
return SidebarStore.singleton;
}
+ initSingleton(options) {
+ const { currentUser, rootPath, editable } = options;
+ this.currentUser = currentUser;
+ this.rootPath = rootPath;
+ this.editable = editable;
+ this.timeEstimate = 0;
+ this.totalTimeSpent = 0;
+ this.humanTimeEstimate = '';
+ this.humanTimeSpent = '';
+ this.assignees = [];
+ this.isFetching = {
+ assignees: true,
+ participants: true,
+ subscriptions: true,
+ };
+ this.isLoading = {};
+ this.autocompleteProjects = [];
+ this.moveToProjectId = 0;
+ this.isLockDialogOpen = false;
+ this.participants = [];
+ this.subscribed = null;
+
+ SidebarStore.singleton = this;
+ }
+
setAssigneeData(data) {
this.isFetching.assignees = false;
if (data.assignees) {
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 3f811c59cb9..95e51bc4e7a 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -2,6 +2,7 @@
import FilesCommentButton from './files_comment_button';
import imageDiffHelper from './image_diff/helpers/index';
+import syntaxHighlight from './syntax_highlight';
const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
@@ -64,7 +65,7 @@ export default class SingleFileDiff {
_this.loadingContent.hide();
if (data.html) {
_this.content = $(data.html);
- _this.content.syntaxHighlight();
+ syntaxHighlight(_this.content);
} else {
_this.hasError = true;
_this.content = $(ERROR_HTML);
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 662d6b36c16..62bdef76c55 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -10,17 +10,15 @@
// <div class="js-syntax-highlight"></div>
//
-$.fn.syntaxHighlight = function() {
- var $children;
-
- if ($(this).hasClass('js-syntax-highlight')) {
+export default function syntaxHighlight(el) {
+ if ($(el).hasClass('js-syntax-highlight')) {
// Given the element itself, apply highlighting
- return $(this).addClass(gon.user_color_scheme);
+ return $(el).addClass(gon.user_color_scheme);
} else {
// Given a parent element, recurse to any of its applicable children
- $children = $(this).find('.js-syntax-highlight');
+ const $children = $(el).find('.js-syntax-highlight');
if ($children.length) {
- return $children.syntaxHighlight();
+ return syntaxHighlight($children);
}
}
-};
+}
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 2fffe09c74e..748caecf153 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -1,5 +1,5 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
-
+import { visitUrl } from './lib/utils/url_utility';
import UsersSelect from './users_select';
import { isMetaClick } from './lib/utils/common_utils';
@@ -150,7 +150,7 @@ export default class Todos {
window.open(todoLink, windowTarget);
} else {
- gl.utils.visitUrl(todoLink);
+ visitUrl(todoLink);
}
}
}
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 7777ed1c3dc..1a0b2c0415b 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
+import { visitUrl } from './lib/utils/url_utility';
export default class TreeView {
constructor() {
@@ -14,7 +15,7 @@ export default class TreeView {
e.preventDefault();
return window.open(path, '_blank');
} else {
- return gl.utils.visitUrl(path);
+ return visitUrl(path);
}
}
});
@@ -56,7 +57,7 @@ export default class TreeView {
} else if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) {
- return gl.utils.visitUrl(path);
+ return visitUrl(path);
}
}
});
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index 5e947769f8a..4fa8c680580 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import d3 from 'd3';
+import { getDayName, getDayDifference } from '../lib/utils/datetime_utility';
const LOADING_HTML = `
<div class="text-center">
@@ -17,7 +18,7 @@ function getSystemDate(systemUtcOffsetSeconds) {
function formatTooltipText({ date, count }) {
const dateObject = new Date(date);
- const dateDayName = gl.utils.getDayName(dateObject);
+ const dateDayName = getDayName(dateObject);
const dateText = dateObject.format('mmm d, yyyy');
let contribText = 'No contributions';
@@ -51,7 +52,7 @@ export default class ActivityCalendar {
const oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
- const days = gl.utils.getDayDifference(oneYearAgo, today);
+ const days = getDayDifference(oneYearAgo, today);
for (let i = 0; i <= days; i += 1) {
const date = new Date(oneYearAgo);
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js
index 1215b265e28..992baa9a1ef 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/users/user_tabs.js
@@ -1,4 +1,6 @@
+import Activities from '../activities';
import ActivityCalendar from './activity_calendar';
+import { localTimeAgo } from '../lib/utils/datetime_utility';
/**
* UserTabs
@@ -138,7 +140,7 @@ export default class UserTabs {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
- gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+ localTimeAgo($('.js-timeago', tabSelector));
},
});
}
@@ -169,7 +171,7 @@ export default class UserTabs {
});
// eslint-disable-next-line no-new
- new gl.Activities();
+ new Activities();
this.loaded.activity = true;
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index e86a0f7e749..ee1a45cc754 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -1,4 +1,5 @@
-import '~/lib/utils/datetime_utility';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon';
@@ -16,7 +17,7 @@ export default {
},
methods: {
formatDate(date) {
- return gl.utils.getTimeago().format(date);
+ return getTimeago().format(date);
},
hasExternalUrls(deployment = {}) {
return deployment.external_url && deployment.external_url_formatted;
@@ -36,7 +37,7 @@ export default {
.then(res => res.json())
.then((res) => {
if (res.redirect_url) {
- gl.utils.visitUrl(res.redirect_url);
+ visitUrl(res.redirect_url);
}
})
.catch(() => {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
index 05c4a28be88..43b2d238f65 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js
@@ -65,10 +65,12 @@ export default {
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
- <h4>
- Set by
- <mr-widget-author :author="mr.setToMWPSBy" />
- to be merged automatically when the pipeline succeeds
+ <h4 class="flex-container-block">
+ <span class="append-right-10">
+ Set by
+ <mr-widget-author :author="mr.setToMWPSBy" />
+ to be merged automatically when the pipeline succeeds
+ </span>
<a
v-if="mr.canCancelAutomaticMerge"
@click.prevent="cancelAutomaticMerge"
@@ -94,8 +96,13 @@ export default {
<p v-if="mr.shouldRemoveSourceBranch">
The source branch will be removed
</p>
- <p v-else>
- The source branch will not be removed
+ <p
+ v-else
+ class="flex-container-block"
+ >
+ <span class="append-right-10">
+ The source branch will not be removed
+ </span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
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 c1f7e64f580..707766e08e4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -1,5 +1,6 @@
import Timeago from 'timeago.js';
import { getStateKey } from '../dependencies';
+import { formatDate } from '../../lib/utils/datetime_utility';
export default class MergeRequestStore {
@@ -122,7 +123,7 @@ export default class MergeRequestStore {
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
- updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
+ updatedAt: formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
};
}
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 4216660da8c..365229ea274 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -36,6 +36,30 @@
required: false,
default: '',
},
+
+ width: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ height: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ y: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ x: {
+ type: Number,
+ required: false,
+ default: null,
+ },
},
computed: {
@@ -51,7 +75,11 @@
<template>
<svg
- :class="[iconSizeClass, cssClasses]">
+ :class="[iconSizeClass, cssClasses]"
+ :width="width"
+ :height="height"
+ :x="x"
+ :y="y">
<use
v-bind="{'xlink:href':spriteHref}"/>
</svg>
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.js
index 643b77e04c7..f37ef1a5ca3 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.js
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.js
@@ -1,3 +1,5 @@
+import { getTimeago } from '../../lib/utils/datetime_utility';
+
export default {
name: 'MemoryGraph',
props: {
@@ -16,7 +18,7 @@ export default {
},
computed: {
getFormattedMedian() {
- const deployedSince = gl.utils.getTimeago().format(this.deploymentTime * 1000);
+ const deployedSince = getTimeago().format(this.deploymentTime * 1000);
return `Deployed ${deployedSince}`;
},
},
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index 47efee64c6e..55f466b7b41 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'popup-dialog',
+ name: 'modal',
props: {
title: {
@@ -38,7 +38,8 @@ export default {
},
primaryButtonLabel: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
submitDisabled: {
type: Boolean,
@@ -74,7 +75,7 @@ export default {
<template>
<div class="modal-open">
<div
- class="modal popup-dialog"
+ class="modal show"
role="dialog"
tabindex="-1"
>
@@ -113,8 +114,9 @@ export default {
{{ closeButtonLabel }}
</button>
<button
+ v-if="primaryButtonLabel"
type="button"
- class="btn pull-right"
+ class="btn pull-right js-primary-button"
:disabled="submitDisabled"
:class="btnKindClass"
@click="emitSubmit(true)">
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
new file mode 100644
index 00000000000..8053c65d498
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -0,0 +1,85 @@
+<script>
+import modal from './modal.vue';
+
+export default {
+ name: 'recaptcha-modal',
+
+ props: {
+ html: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ data() {
+ return {
+ script: {},
+ scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ };
+ },
+
+ components: {
+ modal,
+ },
+
+ methods: {
+ appendRecaptchaScript() {
+ this.removeRecaptchaScript();
+
+ const script = document.createElement('script');
+ script.src = this.scriptSrc;
+ script.classList.add('js-recaptcha-script');
+ script.async = true;
+ script.defer = true;
+
+ this.script = script;
+
+ document.body.appendChild(script);
+ },
+
+ removeRecaptchaScript() {
+ if (this.script instanceof Element) this.script.remove();
+ },
+
+ close() {
+ this.removeRecaptchaScript();
+ this.$emit('close');
+ },
+
+ submit() {
+ this.$el.querySelector('form').submit();
+ },
+ },
+
+ watch: {
+ html() {
+ this.appendRecaptchaScript();
+ },
+ },
+
+ mounted() {
+ window.recaptchaDialogCallback = this.submit.bind(this);
+ },
+};
+</script>
+
+<template>
+<modal
+ kind="warning"
+ class="recaptcha-modal js-recaptcha-modal"
+ :hide-footer="true"
+ :title="__('Please solve the reCAPTCHA')"
+ @toggle="close"
+>
+ <div slot="body">
+ <p>
+ {{__('We want to be sure it is you, please confirm you are not a robot.')}}
+ </p>
+ <div
+ ref="recaptcha"
+ v-html="html"
+ ></div>
+ </div>
+</modal>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index ddc9ddbc3a3..4277d9281a0 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -1,6 +1,13 @@
<script>
+ import { s__ } from '../../locale';
+ import icon from './icon.vue';
import loadingIcon from './loading_icon.vue';
+ const ICON_ON = 'status_success_borderless';
+ const ICON_OFF = 'status_failed_borderless';
+ const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
+ const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
+
export default {
props: {
name: {
@@ -22,19 +29,10 @@
required: false,
default: false,
},
- enabledText: {
- type: String,
- required: false,
- default: 'Enabled',
- },
- disabledText: {
- type: String,
- required: false,
- default: 'Disabled',
- },
},
components: {
+ icon,
loadingIcon,
},
@@ -43,6 +41,15 @@
event: 'change',
},
+ computed: {
+ toggleIcon() {
+ return this.value ? ICON_ON : ICON_OFF;
+ },
+ ariaLabel() {
+ return this.value ? LABEL_ON : LABEL_OFF;
+ },
+ },
+
methods: {
toggleFeature() {
if (!this.disabledInput) this.$emit('change', !this.value);
@@ -60,10 +67,8 @@
/>
<button
type="button"
- aria-label="Toggle"
class="project-feature-toggle"
- :data-enabled-text="enabledText"
- :data-disabled-text="disabledText"
+ :aria-label="ariaLabel"
:class="{
'is-checked': value,
'is-disabled': disabledInput,
@@ -72,6 +77,11 @@
@click="toggleFeature"
>
<loadingIcon class="loading-icon" />
+ <span class="toggle-icon">
+ <icon
+ css-classes="toggle-icon-svg"
+ :name="toggleIcon"/>
+ </span>
</button>
</label>
</template>
diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js
new file mode 100644
index 00000000000..ff1f565e79a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js
@@ -0,0 +1,36 @@
+import recaptchaModal from '../components/recaptcha_modal.vue';
+
+export default {
+ data() {
+ return {
+ showRecaptcha: false,
+ recaptchaHTML: '',
+ };
+ },
+
+ components: {
+ recaptchaModal,
+ },
+
+ methods: {
+ openRecaptcha() {
+ this.showRecaptcha = true;
+ },
+
+ closeRecaptcha() {
+ this.showRecaptcha = false;
+ },
+
+ checkForSpam(data) {
+ if (!data.recaptcha_html) return data;
+
+ this.recaptchaHTML = data.recaptcha_html;
+
+ const spamError = new Error(data.error_message);
+ spamError.name = 'SpamError';
+ spamError.message = 'SpamError';
+
+ throw spamError;
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js
index 20f63ab663c..4e3b9d7b767 100644
--- a/app/assets/javascripts/vue_shared/mixins/timeago.js
+++ b/app/assets/javascripts/vue_shared/mixins/timeago.js
@@ -1,4 +1,4 @@
-import '../../lib/utils/datetime_utility';
+import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
@@ -6,13 +6,13 @@ import '../../lib/utils/datetime_utility';
export default {
methods: {
timeFormated(time) {
- const timeago = gl.utils.getTimeago();
+ const timeago = getTimeago();
return timeago.format(time);
},
tooltipTitle(time) {
- return gl.utils.formatDate(time);
+ return formatDate(time);
},
},
};
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 3f630f82e29..fcc420923f9 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -4,8 +4,8 @@
padding: 1px 5px;
font-size: 12px;
color: $blue-500;
- width: 23px;
- height: 23px;
+ width: 24px;
+ height: 24px;
border: 1px solid $blue-500;
&:hover,
diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss
index b73932eb7e1..8baf7ca23a4 100644
--- a/app/assets/stylesheets/framework/contextual-sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual-sidebar.scss
@@ -1,4 +1,6 @@
.page-with-contextual-sidebar {
+ transition: padding-left $sidebar-transition-duration;
+
@media (min-width: $screen-md-min) {
padding-left: $contextual-sidebar-collapsed-width;
}
@@ -27,8 +29,10 @@
.context-header {
position: relative;
margin-right: 2px;
+ width: $contextual-sidebar-width;
a {
+ transition: padding $sidebar-transition-duration;
font-weight: $gl-font-weight-bold;
display: flex;
align-items: center;
@@ -63,10 +67,10 @@
}
.nav-sidebar {
+ transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
position: fixed;
z-index: 400;
width: $contextual-sidebar-width;
- transition: left $sidebar-transition-duration;
top: $header-height;
bottom: 0;
left: 0;
@@ -74,16 +78,15 @@
box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0);
- &:not(.sidebar-icons-only) {
+ &:not(.sidebar-collapsed-desktop) {
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
box-shadow: inset -2px 0 0 $border-color,
2px 1px 3px $dropdown-shadow-color;
}
}
- &.sidebar-icons-only {
- width: auto;
- min-width: $contextual-sidebar-collapsed-width;
+ &.sidebar-collapsed-desktop {
+ width: $contextual-sidebar-collapsed-width;
.nav-sidebar-inner-scroll {
overflow-x: hidden;
@@ -108,12 +111,11 @@
}
}
- &.nav-sidebar-expanded {
+ &.sidebar-expanded-mobile {
left: 0;
}
a {
- transition: none;
text-decoration: none;
}
@@ -126,9 +128,10 @@
white-space: nowrap;
a {
+ transition: padding $sidebar-transition-duration;
display: flex;
align-items: center;
- padding: 12px 16px;
+ padding: 12px 15px;
color: $gl-text-color-secondary;
}
@@ -288,7 +291,8 @@
> a {
margin-left: 4px;
- padding-left: 12px;
+ // Subtract width of left border on active element
+ padding-left: 11px;
}
.badge {
@@ -313,15 +317,17 @@
.toggle-sidebar-button,
.close-nav-button {
width: $contextual-sidebar-width - 2px;
+ transition: width $sidebar-transition-duration;
position: fixed;
bottom: 0;
- padding: 16px;
+ padding: $gl-padding;
background-color: $gray-light;
border: 0;
border-top: 2px solid $border-color;
color: $gl-text-color-secondary;
display: flex;
align-items: center;
+ line-height: 1;
svg {
margin-right: 8px;
@@ -343,20 +349,21 @@
}
}
+.collapse-text {
+ white-space: nowrap;
+ overflow: hidden;
+}
-.sidebar-icons-only {
+.sidebar-collapsed-desktop {
.context-header {
- height: 61px;
+ height: 60px;
+ width: $contextual-sidebar-collapsed-width;
a {
padding: 10px 4px;
}
}
- li a {
- padding: 12px 15px;
- }
-
.sidebar-top-level-items > li {
&.active a {
padding-left: 12px;
@@ -374,8 +381,8 @@
}
.toggle-sidebar-button {
- width: $contextual-sidebar-collapsed-width - 2px;
padding: 16px;
+ width: $contextual-sidebar-collapsed-width - 2px;
.collapse-text,
.icon-angle-double-left {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8d83554d813..478269f3fcf 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -143,20 +143,48 @@
}
}
+@mixin dropdown-item-hover {
+ background-color: $dropdown-item-hover-bg;
+ color: $gl-text-color;
+ outline: 0;
+
+ // make sure the text color is not overriden
+ &.text-danger {
+ color: $brand-danger;
+ }
+
+ .avatar {
+ border-color: $white-light;
+ }
+}
+
@mixin dropdown-link {
+ background: transparent;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
display: block;
+ font-weight: $gl-font-weight-normal;
position: relative;
- padding: 5px 8px;
+ padding: 8px 16px;
color: $gl-text-color;
- line-height: initial;
- border-radius: 2px;
- white-space: nowrap;
+ line-height: normal;
+ white-space: normal;
overflow: hidden;
+ text-align: left;
+ width: 100%;
+
+ // make sure the text color is not overriden
+ &.text-danger {
+ color: $brand-danger;
+ }
&:hover,
+ &:active,
&:focus,
&.is-focused {
- background-color: $dropdown-link-hover-bg;
+ @include dropdown-item-hover;
+
text-decoration: none;
.badge {
@@ -166,6 +194,13 @@
&.dropdown-menu-user-link {
line-height: 16px;
+ padding-top: 10px;
+ padding-bottom: 7px;
+ white-space: nowrap;
+
+ .dropdown-menu-user-username {
+ display: block;
+ }
}
.icon-play {
@@ -187,8 +222,8 @@
z-index: 300;
min-width: 240px;
max-width: 500px;
- margin-top: 2px;
- margin-bottom: 2px;
+ margin-top: $dropdown-vertical-offset;
+ margin-bottom: 24px;
font-size: 14px;
font-weight: $gl-font-weight-normal;
padding: 8px 0;
@@ -197,6 +232,10 @@
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
+ &.dropdown-open-top {
+ margin-bottom: $dropdown-vertical-offset;
+ }
+
&.dropdown-open-left {
right: 0;
left: auto;
@@ -227,16 +266,27 @@
}
li {
+ display: block;
text-align: left;
list-style: none;
- padding: 0 10px;
+ padding: 0 1px;
+
+ a,
+ button,
+ .menu-item {
+ @include dropdown-link;
+ }
}
.divider {
height: 1px;
- margin: 6px 10px;
+ margin: 6px 0;
padding: 0;
background-color: $dropdown-divider-color;
+
+ &:hover {
+ background-color: $dropdown-divider-color;
+ }
}
.separator {
@@ -247,10 +297,6 @@
background-color: $dropdown-divider-color;
}
- a {
- @include dropdown-link;
- }
-
.dropdown-menu-empty-item a {
&:hover,
&:focus {
@@ -262,7 +308,7 @@
color: $gl-text-color-secondary;
font-size: 13px;
line-height: 22px;
- padding: 0 16px;
+ padding: 8px 16px;
}
&.capitalize-header .dropdown-header {
@@ -277,7 +323,7 @@
.separator + .dropdown-header,
.separator + .dropdown-bold-header {
- padding-top: 2px;
+ padding-top: 10px;
}
.unclickable {
@@ -298,48 +344,28 @@
}
.dropdown-menu li {
- padding: $gl-btn-padding;
cursor: pointer;
+ &.droplab-item-active button {
+ @include dropdown-item-hover;
+ }
+
> a,
> button {
display: flex;
margin: 0;
- padding: 0;
- border-radius: 0;
text-overflow: inherit;
- background-color: inherit;
- color: inherit;
- border: inherit;
text-align: left;
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
-
&.btn .fa:not(:last-child) {
margin-left: 5px;
}
}
- &:hover,
- &:focus {
- background-color: $dropdown-hover-color;
- color: $white-light;
- }
-
&.droplab-item-selected i {
visibility: visible;
}
- &.divider {
- margin: 0 8px;
- padding: 0;
- border-top: $gray-darkest;
- }
-
.icon {
visibility: hidden;
}
@@ -431,11 +457,6 @@
}
}
-.dropdown-menu-user-link {
- padding-top: 10px;
- padding-bottom: 7px;
-}
-
.dropdown-menu-user-full-name {
display: block;
font-weight: $gl-font-weight-normal;
@@ -464,41 +485,44 @@
.dropdown-menu-align-right {
left: auto;
right: 0;
- margin-top: -5px;
}
.dropdown-menu-selectable {
- a {
- padding-left: 26px;
- position: relative;
+ li {
+ a {
+ padding: 8px 40px;
+ position: relative;
+
+ &.is-indeterminate,
+ &.is-active {
+ color: $gl-text-color;
+
+ &::before {
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ transform: translateY(-50%);
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
- &.is-indeterminate,
- &.is-active {
- font-weight: $gl-font-weight-bold;
- color: $gl-text-color;
-
- &::before {
- position: absolute;
- left: 6px;
- top: 50%;
- transform: translateY(-50%);
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ &.dropdown-menu-user-link {
+ &::before {
+ top: 50%;
+ }
+ }
}
- }
- &.is-indeterminate::before {
- content: "\f068";
- }
+ &.is-indeterminate::before {
+ content: "\f068";
+ }
- &.is-active::before {
- content: "\f00c";
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
+ &.is-active::before {
+ content: "\f00c";
+ }
}
}
}
@@ -735,136 +759,6 @@
}
}
-@mixin dropdown-item-hover {
- background-color: $dropdown-item-hover-bg;
- color: $gl-text-color;
-}
-
-// TODO: change global style and remove mixin
-@mixin new-style-dropdown($selector: '') {
- #{$selector}.dropdown-menu,
- #{$selector}.dropdown-menu-nav {
- margin-bottom: 24px;
-
- &.dropdown-open-top {
- margin-bottom: $dropdown-vertical-offset;
- }
-
- li {
- display: block;
- padding: 0 1px;
-
- &:hover {
- background-color: transparent;
- }
-
- &.divider {
- margin: 6px 0;
-
- &:hover {
- background-color: $dropdown-divider-color;
- }
- }
-
- &.dropdown-header {
- padding: 8px 16px;
- }
-
- &.droplab-item-active button {
- @include dropdown-item-hover;
- }
-
- a,
- button,
- .menu-item {
- margin-bottom: 0;
- border-radius: 0;
- box-shadow: none;
- padding: 8px 16px;
- text-align: left;
- white-space: normal;
- width: 100%;
- font-weight: $gl-font-weight-normal;
- line-height: normal;
-
- &.dropdown-menu-user-link {
- white-space: nowrap;
-
- .dropdown-menu-user-username {
- display: block;
- }
- }
-
- // make sure the text color is not overriden
- &.text-danger {
- color: $brand-danger;
- }
-
- &.is-focused,
- &:hover,
- &:active,
- &:focus {
- @include dropdown-item-hover;
-
- background-color: $dropdown-item-hover-bg;
- color: $gl-text-color;
-
- // make sure the text color is not overriden
- &.text-danger {
- color: $brand-danger;
- }
- }
-
- &.is-active {
- font-weight: inherit;
-
- &::before {
- top: 16px;
- }
-
- &.dropdown-menu-user-link::before {
- top: 50%;
- transform: translateY(-50%);
- }
- }
- }
-
- &.dropdown-menu-empty-item a {
- &:hover,
- &:focus {
- background-color: transparent;
- }
- }
- }
-
- &.dropdown-menu-selectable {
- li {
- a {
- padding: 8px 40px;
-
- &.is-indeterminate::before,
- &.is-active::before {
- left: 16px;
- }
- }
- }
- }
- }
-
- #{$selector}.dropdown-menu-align-right {
- margin-top: 2px;
- }
-
- .open {
- #{$selector}.dropdown-menu,
- #{$selector}.dropdown-menu-nav {
- @media (max-width: $screen-xs-max) {
- max-width: 100%;
- }
- }
- }
-}
-
@media (max-width: $screen-xs-max) {
.navbar-gitlab {
li.header-projects,
@@ -891,9 +785,6 @@
}
}
-@include new-style-dropdown('.breadcrumbs-list .dropdown ');
-@include new-style-dropdown('.js-namespace-select + ');
-
header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0;
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 609f33582e1..1588036aeae 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -396,3 +396,8 @@ span.idiff {
.file-fork-suggestion-note {
margin-right: 1.5em;
}
+
+.label-lfs {
+ color: $common-gray-light;
+ border: 1px solid $common-gray-light;
+}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index cec38eea464..2d7465401f1 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -50,8 +50,6 @@
}
.filtered-search-wrapper {
- @include new-style-dropdown;
-
display: -webkit-flex;
display: flex;
@@ -165,16 +163,6 @@
}
}
-.droplab-dropdown li.filtered-search-token {
- padding: 0;
-
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
-}
-
.filtered-search-term {
.name {
background-color: inherit;
@@ -336,21 +324,12 @@
.filtered-search-history-dropdown-content {
max-height: none;
-}
-
-.filtered-search-history-dropdown-item,
-.filtered-search-history-clear-button {
- @include dropdown-link;
-
- overflow: hidden;
- width: 100%;
- margin: 0.5em 0;
- background-color: transparent;
- border: 0;
- text-align: left;
- white-space: nowrap;
- text-overflow: ellipsis;
+ .filtered-search-history-dropdown-item,
+ .filtered-search-history-clear-button {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
}
.filtered-search-history-dropdown-token {
@@ -402,24 +381,9 @@
}
}
-%filter-dropdown-item-btn-hover {
- text-decoration: none;
- outline: 0;
-
- .avatar {
- border-color: $white-light;
- }
-}
-
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
.btn {
- border: 0;
- width: 100%;
- text-align: left;
- padding: 8px 16px;
text-overflow: ellipsis;
- overflow: hidden;
- border-radius: 0;
.fa {
width: 15px;
@@ -434,11 +398,6 @@
height: 17px;
top: 0;
}
-
- &:hover,
- &:focus {
- @extend %filter-dropdown-item-btn-hover;
- }
}
.dropdown-light-content {
@@ -459,17 +418,9 @@
word-break: break-all;
}
}
-
- &.droplab-item-active .btn {
- @extend %filter-dropdown-item-btn-hover;
- }
}
.filter-dropdown-loading {
padding: 8px 16px;
text-align: center;
}
-
-.issues-details-filters {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f985a3aea5c..29714e348a0 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -1,10 +1,4 @@
-.content-wrapper.page-with-new-nav {
- margin-top: $header-height;
-}
-
.navbar-gitlab {
- @include new-style-dropdown;
-
&.navbar-gitlab {
padding: 0 16px;
z-index: 1000;
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 78a8e57ddbb..aa2d30a3cef 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -19,6 +19,13 @@
max-width: 425px;
width: 100%;
}
+
+ &.svg-250 {
+ img,
+ svg {
+ width: 250px;
+ }
+ }
}
@mixin svg-size($size) {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 1537b0744cc..1d8bd26cf1a 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -24,10 +24,14 @@
font-size: $gl-font-size;
line-height: 25px;
- &.status-box-closed {
+ &.status-box-mr-closed {
background-color: $gl-danger;
}
+ &.status-box-issue-closed {
+ background-color: $gl-primary;
+ }
+
&.status-box-merged {
background-color: $gl-primary;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index cb324ccc440..3f0268541a4 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -24,6 +24,7 @@ body {
}
.content-wrapper {
+ margin-top: $header-height;
padding-bottom: 100px;
}
@@ -105,11 +106,11 @@ body {
}
}
-.page-with-sidebar > .content-wrapper {
+.layout-page > .content-wrapper {
min-height: calc(100vh - #{$header-height});
}
-.with-performance-bar .page-with-sidebar {
+.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index e6e6c4c3963..f79a71221c4 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -132,8 +132,6 @@ ul.content-list {
}
.controls {
- @include new-style-dropdown;
-
float: right;
> .control-text {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 600a1f53b58..a12f28efce6 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -111,21 +111,4 @@
aside:not(.right-sidebar) {
display: none;
}
-
- .show-aside {
- display: block !important;
- }
-}
-
-.show-aside {
- display: none;
- position: fixed;
- right: 0;
- top: 30%;
- padding: 5px 15px;
- background: $show-aside-bg;
- font-size: 20px;
- color: $show-aside-color;
- z-index: 100;
- box-shadow: 0 1px 2px $show-aside-shadow;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 5c9838c1029..1be66d0ab21 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -44,7 +44,21 @@ body.modal-open {
}
}
-.modal.popup-dialog {
- display: block;
+.modal {
+ background-color: $black-transparent;
+ z-index: 2100;
+
+ @media (min-width: $screen-md-min) {
+ .modal-dialog {
+ margin: 30px auto;
+ }
+ }
}
+.recaptcha-modal .recaptcha-form {
+ display: inline-block;
+
+ .recaptcha {
+ margin: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
index 8498b37abe4..5f67126bafa 100644
--- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss
+++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
@@ -86,8 +86,6 @@
}
.nav-controls {
- @include new-style-dropdown;
-
display: inline-block;
float: right;
text-align: right;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 792981fdc48..0742c0a2a09 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -50,6 +50,11 @@
&:not(.disabled) {
cursor: pointer;
}
+
+ svg {
+ width: $gl-padding;
+ height: $gl-padding;
+ }
}
}
@@ -139,10 +144,6 @@
}
}
-.issuable-sidebar {
- @include new-style-dropdown;
-}
-
.pikaday-container {
.pika-single {
margin-top: 2px;
diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
index 71765da3908..0cd83df218f 100644
--- a/app/assets/stylesheets/framework/toggle.scss
+++ b/app/assets/stylesheets/framework/toggle.scss
@@ -27,7 +27,7 @@
border: 0;
outline: 0;
display: block;
- width: 100px;
+ width: 50px;
height: 24px;
cursor: pointer;
user-select: none;
@@ -42,31 +42,31 @@
background: none;
}
- &::before {
- color: $feature-toggle-text-color;
- font-size: 12px;
- line-height: 24px;
- position: absolute;
- top: 0;
- left: 25px;
- right: 5px;
- text-align: center;
- overflow: hidden;
- text-overflow: ellipsis;
- animation: animate-disabled .2s ease-in;
- content: attr(data-disabled-text);
- }
-
- &::after {
+ .toggle-icon {
position: relative;
display: block;
- content: "";
- width: 22px;
- height: 18px;
left: 0;
border-radius: 9px;
background: $feature-toggle-color;
transition: all .2s ease;
+
+ &,
+ .toggle-icon-svg {
+ width: 18px;
+ height: 18px;
+ }
+
+ .toggle-icon-svg {
+ fill: $feature-toggle-color-disabled;
+ }
+
+ .toggle-status-checked {
+ display: none;
+ }
+
+ .toggle-status-unchecked {
+ display: inline;
+ }
}
.loading-icon {
@@ -77,11 +77,10 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-
}
&.is-loading {
- &::before {
+ .toggle-icon {
display: none;
}
@@ -100,15 +99,20 @@
&.is-checked {
background: $feature-toggle-color-enabled;
- &::before {
- left: 5px;
- right: 25px;
- animation: animate-enabled .2s ease-in;
- content: attr(data-enabled-text);
- }
+ .toggle-icon {
+ left: calc(100% - 18px);
- &::after {
- left: calc(100% - 22px);
+ .toggle-icon-svg {
+ fill: $feature-toggle-color-enabled;
+ }
+
+ .toggle-status-checked {
+ display: inline;
+ }
+
+ .toggle-status-unchecked {
+ display: none;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0817cce114c..11c1aeea871 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -343,8 +343,6 @@ a > code {
@extend .ref-name;
}
-@include new-style-dropdown('.git-revision-dropdown');
-
/**
* Apply Markdown typography
*
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4f99c27eff1..b84d6c140be 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -5,10 +5,9 @@ $grid-size: 8px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 250px;
-$sidebar-transition-duration: .15s;
+$sidebar-transition-duration: .3s;
$sidebar-breakpoint: 1024px;
$default-transition-duration: .15s;
-$right-sidebar-transition-duration: .3s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
@@ -246,9 +245,6 @@ $btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px;
$issue-status-expired: $orange-500;
$issuable-sidebar-color: $gl-text-color-secondary;
-$show-aside-bg: #eee;
-$show-aside-color: #777;
-$show-aside-shadow: #ddd;
$group-path-color: #999;
$namespace-kind-color: #aaa;
$panel-heading-link-color: #777;
@@ -722,7 +718,7 @@ $issuable-warning-icon-margin: 4px;
Image Commenting cursor
*/
$image-comment-cursor-left-offset: 12;
-$image-comment-cursor-top-offset: 30;
+$image-comment-cursor-top-offset: 12;
/*
Popup
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 3683afa07de..2803144ef1d 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -57,7 +57,7 @@
position: relative;
@media (min-width: $screen-sm-min) {
- transition: width $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration;
width: 100%;
&.is-compact {
@@ -415,7 +415,7 @@
margin: 5px;
}
-.page-with-contextual-sidebar.page-with-sidebar .issue-boards-sidebar {
+.page-with-contextual-sidebar.layout-page .issue-boards-sidebar {
.issuable-sidebar-header {
position: relative;
}
@@ -453,8 +453,8 @@
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
- transition: width $right-sidebar-transition-duration,
- padding $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration,
+ padding $sidebar-transition-duration;
}
&.boards-sidebar-slide-enter,
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index f139f4ab650..98d460339cd 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -323,8 +323,6 @@
}
.build-dropdown {
- @include new-style-dropdown;
-
margin: $gl-padding 0;
padding: 0;
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index c303f016ff9..88d44131d5b 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -13,8 +13,6 @@
max-width: 100%;
}
-@include new-style-dropdown('.clusters-dropdown ');
-
.clusters-container {
.nav-bar-right {
padding: $gl-padding-top $gl-padding;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 292e0ad394b..3b35beb7695 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -1,6 +1,4 @@
#cycle-analytics {
- @include new-style-dropdown;
-
max-width: 1000px;
margin: 24px auto 0;
position: relative;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 52e4d904b9b..2f2c04206e2 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -32,8 +32,6 @@
}
.detail-page-header-actions {
- @include new-style-dropdown;
-
align-self: center;
flex-shrink: 0;
flex: 0 0 auto;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 848d7f144dc..60b07537799 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -581,8 +581,6 @@
}
.commit-stat-summary {
- @include new-style-dropdown;
-
@media (min-width: $screen-sm-min) {
margin-left: -$gl-padding;
padding-left: $gl-padding;
@@ -732,18 +730,18 @@
.frame.click-to-comment {
position: relative;
- cursor: image-url('icon_image_comment.svg')
+ cursor: image-url('illustrations/image_comment_light_cursor.svg')
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor
- cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x)
+ cursor: -webkit-image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x, image-url('illustrations/image_comment_light_cursor@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);
+ height: (2px * $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;
@@ -778,15 +776,20 @@
.frame .badge,
.frame .image-comment-badge {
// Center align badges on the frame
- transform: translate3d(-50%, -50%, 0);
+ transform: translate(-50%, -50%);
}
.image-comment-badge {
- @include btn-comment-icon;
position: absolute;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ background: none;
+ border: 0;
- &.inverted {
- border-color: $white-light;
+ > svg {
+ width: 100%;
+ height: 100%;
}
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index c586dab4cf2..8ecda50602d 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -204,8 +204,6 @@
.gitlab-ci-yml-selector,
.dockerfile-selector,
.template-type-selector {
- @include new-style-dropdown;
-
display: inline-block;
vertical-align: top;
font-family: $regular_font;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index b0795353ec1..f4882305c57 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -12,8 +12,6 @@
.environments-container {
.ci-table {
- @include new-style-dropdown;
-
.deployment-column {
> span {
word-break: break-all;
@@ -201,8 +199,9 @@
stroke-width: 1;
}
-.deploy-info-text {
- dominant-baseline: text-before-edge;
+.divider-line {
+ stroke-width: 1;
+ stroke: $gray-darkest;
}
.prometheus-state {
@@ -312,6 +311,20 @@
stroke: $gray-darker;
}
+ .deploy-info-text {
+ dominant-baseline: text-before-edge;
+ font-size: 12px;
+ }
+
+ .deploy-info-text-link {
+ font-family: $monospace_font;
+ fill: $gl-link-color;
+
+ &:hover {
+ fill: $gl-link-hover-color;
+ }
+ }
+
@media (max-width: $screen-sm-max) {
.label-axis-text,
.text-metric-usage,
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 11ee1232bfe..e19196e0c41 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -126,7 +126,7 @@
top: $header-height;
bottom: 0;
right: 0;
- transition: width $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration;
background: $gray-light;
z-index: 200;
overflow: hidden;
@@ -470,7 +470,8 @@
}
}
- .milestone-title span {
+ .milestone-title span,
+ .collapse-truncated-title {
@include str-truncated(100%);
display: block;
margin: 0 4px;
@@ -487,12 +488,6 @@
}
}
- .dropdown-content {
- a:hover {
- color: inherit;
- }
- }
-
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
@@ -511,10 +506,6 @@
}
}
-.sidebar-move-issue-dropdown {
- @include new-style-dropdown;
-}
-
.sidebar-move-issue-confirmation-button {
width: 100%;
@@ -619,11 +610,19 @@
}
.issuable-status-box {
- float: none;
- display: inline-block;
+ align-self: stretch;
+ display: flex;
+ justify-content: center;
+ align-items: center;
margin-top: 0;
- height: auto;
- align-self: center;
+ padding-left: 9px;
+ padding-right: 9px;
+
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ height: auto;
+ align-self: center;
+ }
}
.issuable-gutter-toggle {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index af1df8b8802..c48e58af691 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -142,8 +142,6 @@ ul.related-merge-requests > li {
}
.issue-form {
- @include new-style-dropdown;
-
.select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 92abe82df4c..e8cd8a4905c 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -116,8 +116,6 @@
}
.manage-labels-list {
- @include new-style-dropdown;
-
> li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light;
cursor: move;
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 18c48405ecd..3422829de58 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -58,8 +58,6 @@
}
.member-form-control {
- @include new-style-dropdown;
-
@media (max-width: $screen-xs-max) {
padding-bottom: 5px;
margin-left: 0;
@@ -73,8 +71,6 @@
}
.member-search-form {
- @include new-style-dropdown;
-
position: relative;
@media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2afb17334e3..e75a35d78ad 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -485,8 +485,6 @@
}
.mr-source-target {
- @include new-style-dropdown;
-
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -608,8 +606,6 @@
}
.mr-version-controls {
- @include new-style-dropdown;
-
position: relative;
background: $gray-light;
color: $gl-text-color;
@@ -727,7 +723,3 @@
font-size: 16px;
}
}
-
-.merge-request-form {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index ebb5d121433..6d4ccd53e12 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -23,8 +23,6 @@
.new-note,
.note-edit-form {
.note-form-actions {
- @include new-style-dropdown;
-
position: relative;
margin: $gl-padding 0 0;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4d5613c292b..26e6e8688b6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -490,8 +490,6 @@ ul.notes {
}
.note-actions {
- @include new-style-dropdown;
-
align-self: flex-start;
flex-shrink: 0;
display: inline-flex;
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index c28b1e68008..bdf07a99daf 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -14,7 +14,3 @@
font-size: 18px;
}
}
-
-.notification-form {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index cb24274c612..9805fc4f882 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -286,8 +286,6 @@
// Pipeline visualization
.pipeline-actions {
- @include new-style-dropdown;
-
border-bottom: 0;
}
@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle {
}
}
-@include new-style-dropdown('.big-pipeline-graph-dropdown-menu');
-@include new-style-dropdown('.mini-pipeline-graph-dropdown-menu');
-
// dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu {
@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: normal;
line-height: $line-height-base;
white-space: nowrap;
- border-radius: 3px;
.ci-job-name-component {
align-items: center;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 674588752d2..6f4c678c4b8 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -323,8 +323,6 @@
}
.project-repo-buttons {
- @include new-style-dropdown;
-
.project-action-button .dropdown-menu {
max-height: 250px;
overflow-y: auto;
@@ -898,8 +896,6 @@ pre.light-well {
.new-protected-branch,
.new-protected-tag {
- @include new-style-dropdown;
-
label {
margin-top: 6px;
font-weight: $gl-font-weight-normal;
@@ -919,8 +915,6 @@ pre.light-well {
.protected-branches-list,
.protected-tags-list {
- @include new-style-dropdown;
-
margin-bottom: 30px;
.settings-message {
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 402412eae71..6eb92c7baee 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -1,16 +1,3 @@
-.modal.popup-dialog {
- display: block;
- background-color: $black-transparent;
- z-index: 2100;
-
- @media (min-width: $screen-md-min) {
- .modal-dialog {
- width: 600px;
- margin: 30px auto;
- }
- }
-}
-
.project-refs-form,
.project-refs-target-form {
display: inline-block;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index fe455a04960..49c8e546bf2 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -116,11 +116,6 @@ input[type="checkbox"]:hover {
opacity: 0;
display: block;
left: -5px;
- padding: 0;
-
- ul {
- padding: 10px 0;
- }
}
.dropdown-content {
@@ -185,8 +180,6 @@ input[type="checkbox"]:hover {
}
.search-holder {
- @include new-style-dropdown;
-
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 2139a029fc7..a79772ea37b 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -265,7 +265,3 @@
font-weight: $gl-font-weight-bold;
}
}
-
-.todos-filters {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5d14323e4bc..e0ee7e9aa3d 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,6 +1,4 @@
.tree-holder {
- @include new-style-dropdown;
-
.nav-block {
margin: 10px 0;
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 2ce26de1768..a94726887d9 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,4 +1,6 @@
class Admin::GroupsController < Admin::ApplicationController
+ include MembersPresentation
+
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController
def show
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
- @members = @group.members.order("access_level DESC").page(params[:members_page])
- @requesters = AccessRequestsFinder.new(@group).execute(current_user)
+ @members = present_members(
+ @group.members.order("access_level DESC").page(params[:members_page]))
+ @requesters = present_members(
+ AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page])
end
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 65a17828feb..61247b280b3 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -5,7 +5,7 @@ class Admin::HealthCheckController < Admin::ApplicationController
end
def reset_storage_health
- Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ Gitlab::Git::Storage::FailureInfo.reset_all!
redirect_to admin_health_check_path,
notice: _('Git storage health information has been reset')
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 50cf2643390..3afe66c3566 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,4 +1,6 @@
class Admin::ProjectsController < Admin::ApplicationController
+ include MembersPresentation
+
before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer]
@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController
def show
if @group
- @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
+ @group_members = present_members(
+ @group.members.order("access_level DESC").page(params[:group_members_page]))
end
- @project_members = @project.members.page(params[:project_members_page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_members = present_members(
+ @project.members.page(params[:project_members_page]))
+ @requesters = present_members(
+ AccessRequestsFinder.new(@project).execute(current_user))
end
def transfer
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
index 2c9c095a5d7..a145049dc7d 100644
--- a/app/controllers/concerns/boards_responses.rb
+++ b/app/controllers/concerns/boards_responses.rb
@@ -24,11 +24,11 @@ module BoardsResponses
end
def respond_with_boards
- respond_with(@boards)
+ respond_with(@boards) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def respond_with_board
- respond_with(@board)
+ respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def respond_with(resource)
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 782f0be9c4a..6f4fdcdaa4f 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -1,6 +1,8 @@
module CreatesCommit
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
if can?(current_user, :push_code, @project)
@project_to_commit_into = @project
@@ -45,6 +47,7 @@ module CreatesCommit
end
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def authorize_edit_tree!
return if can_collaborate_with_project?
@@ -77,6 +80,7 @@ module CreatesCommit
end
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def new_merge_request_path
project_new_merge_request_path(
@project_to_commit_into,
@@ -88,20 +92,28 @@ module CreatesCommit
}
)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def existing_merge_request_path
- project_merge_request_path(@project, @merge_request)
+ project_merge_request_path(@project, @merge_request) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_request_exists?
- return @merge_request if defined?(@merge_request)
-
- @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
- .find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch)
+ strong_memoize(:merge_request) do
+ MergeRequestsFinder.new(current_user, project_id: @project.id)
+ .execute
+ .opened
+ .find_by(
+ source_project_id: @project_to_commit_into,
+ source_branch: @branch_name,
+ target_branch: @start_branch)
+ end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def different_project?
- @project_to_commit_into != @project
+ @project_to_commit_into != @project # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def create_merge_request?
@@ -109,6 +121,6 @@ module CreatesCommit
# as the target branch in the same project,
# we don't want to create a merge request.
params[:create_merge_request].present? &&
- (different_project? || @start_branch != @branch_name)
+ (different_project? || @start_branch != @branch_name) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index 9d4f97aa443..b10147835f3 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -1,4 +1,5 @@
module GroupTree
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups)
@groups = if params[:filter].present?
Gitlab::GroupHierarchy.new(groups.search(params[:filter]))
@@ -20,5 +21,6 @@ module GroupTree
render json: serializer.represent(@groups)
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 744e448e8df..c3013884369 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -17,15 +17,15 @@ module IssuableActions
end
def update
- @issuable = update_service.execute(issuable)
+ @issuable = update_service.execute(issuable) # rubocop:disable Gitlab/ModuleWithInstanceVariables
respond_to do |format|
format.html do
- recaptcha_check_with_fallback { render :edit }
+ recaptcha_check_if_spammable { render :edit }
end
format.json do
- render_entity_json
+ recaptcha_check_if_spammable(false) { render_entity_json }
end
end
@@ -80,10 +80,16 @@ module IssuableActions
private
+ def recaptcha_check_if_spammable(should_redirect = true, &block)
+ return yield unless issuable.is_a? Spammable
+
+ recaptcha_check_with_fallback(should_redirect, &block)
+ end
+
def render_conflict_response
respond_to do |format|
format.html do
- @conflict = true
+ @conflict = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
render :edit
end
@@ -98,7 +104,7 @@ module IssuableActions
end
def labels
- @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+ @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def authorize_destroy_issuable!
@@ -108,7 +114,7 @@ module IssuableActions
end
def authorize_admin_issuable!
- unless can?(current_user, :"admin_#{resource_name}", @project)
+ unless can?(current_user, :"admin_#{resource_name}", @project) # rubocop:disable Gitlab/ModuleWithInstanceVariables
return access_denied!
end
end
@@ -142,6 +148,7 @@ module IssuableActions
@resource_name ||= controller_name.singularize
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_entity_json
if @issuable.valid?
render json: serializer.represent(@issuable)
@@ -149,6 +156,7 @@ module IssuableActions
render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def serializer
raise NotImplementedError
@@ -159,6 +167,6 @@ module IssuableActions
end
def parent
- @project || @group
+ @project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index f3c9251225f..b25e753a5ad 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -2,6 +2,7 @@ module IssuableCollections
extend ActiveSupport::Concern
include SortingHelper
include Gitlab::IssuableMetadata
+ include Gitlab::Utils::StrongMemoize
included do
helper_method :finder
@@ -9,6 +10,7 @@ module IssuableCollections
private
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_issuables_index
@issuables = issuables_collection
@issuables = @issuables.page(params[:page])
@@ -33,6 +35,7 @@ module IssuableCollections
@users.push(author) if author
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def issuables_collection
finder.execute.preload(preload_for_collection)
@@ -41,7 +44,7 @@ module IssuableCollections
def redirect_out_of_range(total_pages)
return false if total_pages.zero?
- out_of_range = @issuables.current_page > total_pages
+ out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
if out_of_range
redirect_to(url_for(params.merge(page: total_pages, only_path: true)))
@@ -51,7 +54,7 @@ module IssuableCollections
end
def issuable_page_count
- page_count_for_relation(@issuables, finder.row_count)
+ page_count_for_relation(@issuables, finder.row_count) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def page_count_for_relation(relation, row_count)
@@ -66,6 +69,7 @@ module IssuableCollections
finder_class.new(current_user, filter_params)
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_params
set_sort_order_from_cookie
set_default_state
@@ -90,6 +94,7 @@ module IssuableCollections
@filter_params.permit(IssuableFinder::VALID_PARAMS)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def set_default_state
params[:state] = 'opened' if params[:state].blank?
@@ -129,9 +134,9 @@ module IssuableCollections
end
def finder
- return @finder if defined?(@finder)
-
- @finder = issuable_finder_for(@finder_type)
+ strong_memoize(:finder) do
+ issuable_finder_for(@finder_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
end
def collection_type
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index ad594903331..d4cccbe6442 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -2,6 +2,7 @@ module IssuesAction
extend ActiveSupport::Concern
include IssuableCollections
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def issues
@finder_type = IssuesFinder
@label = finder.labels.first
@@ -17,4 +18,5 @@ module IssuesAction
format.atom { render layout: 'xml.atom' }
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/members_presentation.rb b/app/controllers/concerns/members_presentation.rb
new file mode 100644
index 00000000000..c0622516fd3
--- /dev/null
+++ b/app/controllers/concerns/members_presentation.rb
@@ -0,0 +1,11 @@
+module MembersPresentation
+ extend ActiveSupport::Concern
+
+ def present_members(members)
+ Gitlab::View::Presenter::Factory.new(
+ members,
+ current_user: current_user,
+ presenter_class: MembersPresenter
+ ).fabricate!
+ end
+end
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index 8b569a01afd..4d44df3bba9 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -2,6 +2,7 @@ module MergeRequestsAction
extend ActiveSupport::Concern
include IssuableCollections
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_requests
@finder_type = MergeRequestsFinder
@label = finder.labels.first
@@ -10,6 +11,7 @@ module MergeRequestsAction
@issuable_meta_data = issuable_meta_data(@merge_requests, collection_type)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
private
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 081f3336780..d92cf8b4894 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -6,7 +6,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", {
- merge_requests: @milestone.sorted_merge_requests,
+ merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: true
})
end
@@ -18,7 +18,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_participants_tab", {
- users: @milestone.participants
+ users: @milestone.participants # rubocop:disable Gitlab/ModuleWithInstanceVariables
})
end
end
@@ -29,7 +29,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_labels_tab", {
- labels: @milestone.labels
+ labels: @milestone.labels # rubocop:disable Gitlab/ModuleWithInstanceVariables
})
end
end
@@ -43,6 +43,7 @@ module MilestoneActions
}
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def milestone_redirect_path
if @project
project_milestone_path(@project, @milestone)
@@ -52,4 +53,5 @@ module MilestoneActions
dashboard_milestone_path(@milestone.safe_title, title: @milestone.title)
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index be2e1b47feb..e82a5650935 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -1,5 +1,6 @@
module NotesActions
include RendersNotes
+ include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
included do
@@ -30,6 +31,7 @@ module NotesActions
render json: notes_json
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def create
create_params = note_params.merge(
merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
@@ -47,7 +49,9 @@ module NotesActions
format.html { redirect_back_or_default }
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
@@ -60,6 +64,7 @@ module NotesActions
format.html { redirect_back_or_default }
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def destroy
if note.editable?
@@ -138,7 +143,7 @@ module NotesActions
end
else
template = "discussions/_diff_discussion"
- @fresh_discussion = true
+ @fresh_discussion = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
locals = { discussions: [discussion], on_image: on_image }
end
@@ -191,7 +196,7 @@ module NotesActions
end
def noteable
- @noteable ||= notes_finder.target || @note&.noteable
+ @noteable ||= notes_finder.target || @note&.noteable # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def require_noteable!
@@ -211,20 +216,21 @@ module NotesActions
end
def note_project
- return @note_project if defined?(@note_project)
- return nil unless project
+ strong_memoize(:note_project) do
+ return nil unless project
- note_project_id = params[:note_project_id]
+ note_project_id = params[:note_project_id]
- @note_project =
- if note_project_id.present?
- Project.find(note_project_id)
- else
- project
- end
+ the_project =
+ if note_project_id.present?
+ Project.find(note_project_id)
+ else
+ project
+ end
- return access_denied! unless can?(current_user, :create_note, @note_project)
+ return access_denied! unless can?(current_user, :create_note, the_project)
- @note_project
+ the_project
+ end
end
end
diff --git a/app/controllers/concerns/oauth_applications.rb b/app/controllers/concerns/oauth_applications.rb
index 9849aa93fa6..f0a68f23566 100644
--- a/app/controllers/concerns/oauth_applications.rb
+++ b/app/controllers/concerns/oauth_applications.rb
@@ -14,6 +14,6 @@ module OauthApplications
end
def load_scopes
- @scopes = Doorkeeper.configuration.scopes
+ @scopes ||= Doorkeeper.configuration.scopes
end
end
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index e9b9e9b38bc..90bb7a87b45 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -1,6 +1,7 @@
module PreviewMarkdown
extend ActiveSupport::Concern
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute
@@ -20,4 +21,5 @@ module PreviewMarkdown
}
}
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb
index bb2c1dfa00a..fb41dc1e8a8 100644
--- a/app/controllers/concerns/renders_commits.rb
+++ b/app/controllers/concerns/renders_commits.rb
@@ -1,6 +1,6 @@
module RendersCommits
def prepare_commits_for_rendering(commits)
- Banzai::CommitRenderer.render(commits, @project, current_user)
+ Banzai::CommitRenderer.render(commits, @project, current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
commits
end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index 824ad06465c..e7ef297879f 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -1,4 +1,5 @@
module RendersNotes
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
@@ -7,6 +8,7 @@ module RendersNotes
notes
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
private
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index be2e6c7f193..3d61458c064 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -66,7 +66,7 @@ module ServiceParams
FILTER_BLANK_PARAMS = [:password].freeze
def service_params
- dynamic_params = @service.event_channel_names + @service.event_names
+ dynamic_params = @service.event_channel_names + @service.event_names # rubocop:disable Gitlab/ModuleWithInstanceVariables
service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params)
if service_params[:service].is_a?(Hash)
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index ffea712a833..9095cc7f783 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -4,6 +4,7 @@ module SnippetsActions
def edit
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def raw
disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
@@ -14,6 +15,7 @@ module SnippetsActions
filename: @snippet.sanitized_file_name
)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
private
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index ada0dde87fb..922aa58a00f 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -2,6 +2,7 @@ module SpammableActions
extend ActiveSupport::Concern
include Recaptcha::Verify
+ include Gitlab::Utils::StrongMemoize
included do
before_action :authorize_submit_spammable!, only: :mark_as_spam
@@ -18,13 +19,13 @@ module SpammableActions
private
def ensure_spam_config_loaded!
- return @spam_config_loaded if defined?(@spam_config_loaded)
-
- @spam_config_loaded = Gitlab::Recaptcha.load_configurations!
+ strong_memoize(:spam_config_loaded) do
+ Gitlab::Recaptcha.load_configurations!
+ end
end
- def recaptcha_check_with_fallback(&fallback)
- if spammable.valid?
+ def recaptcha_check_with_fallback(should_redirect = true, &fallback)
+ if should_redirect && spammable.valid?
redirect_to spammable_path
elsif render_recaptcha?
ensure_spam_config_loaded!
@@ -33,7 +34,18 @@ module SpammableActions
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
- render :verify
+ respond_to do |format|
+ format.html do
+ render :verify
+ end
+
+ format.json do
+ locals = { spammable: spammable, script: false, has_submit: false }
+ recaptcha_html = render_to_string(partial: 'shared/recaptcha_form', formats: :html, locals: locals)
+
+ render json: { recaptcha_html: recaptcha_html }
+ end
+ end
else
yield
end
diff --git a/app/controllers/concerns/toggle_subscription_action.rb b/app/controllers/concerns/toggle_subscription_action.rb
index 92cb534343e..776583579e8 100644
--- a/app/controllers/concerns/toggle_subscription_action.rb
+++ b/app/controllers/concerns/toggle_subscription_action.rb
@@ -12,7 +12,7 @@ module ToggleSubscriptionAction
private
def subscribable_project
- @project || raise(NotImplementedError)
+ @project ||= raise(NotImplementedError)
end
def subscribable_resource
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index ed253042701..230bbe4b1aa 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -6,6 +6,7 @@ module WithPerformanceBar
end
def peek_enabled?
+ return true if Rails.env.development?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
if RequestStore.active?
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 8fc234a62b1..21e77431176 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -1,5 +1,6 @@
class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions
+ include MembersPresentation
include SortingHelper
# Authorize
@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50)
- @members.includes(:user)
+ @members = present_members(@members.includes(:user))
- @requesters = AccessRequestsFinder.new(@group).execute(current_user)
+ @requesters = present_members(
+ AccessRequestsFinder.new(@group).execute(current_user))
@group_member = @group.group_members.new
end
def update
- @group_member = @group.group_members.find(params[:id])
+ @group_member = @group.members_and_requesters.find(params[:id])
+ .present(current_user: current_user)
return render_403 unless can?(current_user, :update_group_member, @group_member)
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index 98c2aaa3526..a931b456a93 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,5 +1,5 @@
class HealthController < ActionController::Base
- protect_from_forgery with: :exception
+ protect_from_forgery with: :exception, except: :storage_check
include RequiresWhitelistedMonitoringClient
CHECKS = [
@@ -23,6 +23,15 @@ class HealthController < ActionController::Base
render_check_results(results)
end
+ def storage_check
+ results = Gitlab::Git::Storage::Checker.check_all
+
+ render json: {
+ check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
+ results: results
+ }
+ end
+
private
def render_check_results(results)
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 6d9873e38df..346eab4ba19 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -8,7 +8,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save
- flash[:personal_access_token] = @personal_access_token.token
+ PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else
set_index_vars
@@ -43,5 +43,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
+
+ @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 0907daacbc3..4a7879db313 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
STATUS_POLLING_INTERVAL = 10_000
def index
- @scope = params[:scope] || 'all'
- @clusters = ClustersFinder.new(project, current_user, @scope).execute.page(params[:page])
- @active_count = ClustersFinder.new(project, current_user, :active).execute.count
- @inactive_count = ClustersFinder.new(project, current_user, :inactive).execute.count
- @all_count = @active_count + @inactive_count
+ clusters = ClustersFinder.new(project, current_user, :all).execute
+ @clusters = clusters.page(params[:page]).per(20)
end
def new
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 1c4c09c772f..4865ec3dfe5 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -110,7 +110,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),
- notice: "Build has been successfully erased!"
+ notice: "Job has been successfully erased!"
else
respond_422
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 1511fc08c89..dc524b790a0 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -9,7 +9,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
before_action :build_merge_request, except: [:create]
def new
- define_new_vars
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40934
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ define_new_vars
+ end
end
def create
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 627cb2bd93c..5940fae8dd0 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -11,7 +11,7 @@ class Projects::NotesController < Projects::ApplicationController
# Controller actions are returned from AbstractController::Base and methods of parent classes are
# excluded in order to return only specific controller related methods.
# That is ok for the app (no :create method in ancestors)
- # but fails for tests because there is a :create method on FactoryGirl (one of the ancestors)
+ # but fails for tests because there is a :create method on FactoryBot (one of the ancestors)
#
# see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
#
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d925dcd21ff..d7372beb9d3 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,5 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
include MembershipActions
+ include MembersPresentation
include SortingHelper
# Authorize
@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
- @project_members = @project_members.sort(@sort).page(params[:page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_members = present_members(@project_members.sort(@sort).page(params[:page]))
+ @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new
end
def update
- @project_member = @project.project_members.find(params[:id])
+ @project_member = @project.members_and_requesters.find(params[:id])
+ .present(current_user: current_user)
return render_403 unless can?(current_user, :update_project_member, @project_member)
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index f3719059f88..f752a46f828 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -26,6 +26,7 @@ class Projects::TreeController < Projects::ApplicationController
respond_to do |format|
format.html do
+ lfs_blob_ids
@last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3882fa4791d..6f609348402 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -9,6 +9,7 @@ class ProjectsController < Projects::ApplicationController
before_action :repository, except: [:index, :new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
+ before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
# Authorize
@@ -272,7 +273,7 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo?
else
- if @project.wiki_enabled?
+ if can?(current_user, :read_wiki, @project)
@project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dccde46fa33..b12ea760668 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -124,17 +124,6 @@ module ApplicationSettingsHelper
_('The number of attempts GitLab will make to access a storage.')
end
- def circuitbreaker_backoff_threshold_help_text
- _("The number of failures after which GitLab will start temporarily "\
- "disabling access to a storage shard on a host")
- end
-
- def circuitbreaker_failure_wait_time_help_text
- _("When access to a storage fails. GitLab will prevent access to the "\
- "storage for the time specified here. This allows the filesystem to "\
- "recover. Repositories on failing shards are temporarly unavailable")
- end
-
def circuitbreaker_failure_reset_time_help_text
_("The time in seconds GitLab will keep failure information. When no "\
"failures occur during this time, information about the mount is reset.")
@@ -145,6 +134,11 @@ module ApplicationSettingsHelper
"timeout error will be raised.")
end
+ def circuitbreaker_check_interval_help_text
+ _("The time in seconds between storage checks. When a previous check did "\
+ "complete yet, GitLab will skip a check.")
+ end
+
def visible_attributes
[
:admin_notification_email,
@@ -154,10 +148,9 @@ module ApplicationSettingsHelper
:akismet_enabled,
:auto_devops_enabled,
:circuitbreaker_access_retries,
- :circuitbreaker_backoff_threshold,
+ :circuitbreaker_check_interval,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
- :circuitbreaker_failure_wait_time,
:circuitbreaker_storage_timeout,
:clientside_sentry_dsn,
:clientside_sentry_enabled,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 18075ee8be7..556ed233ccf 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -118,20 +118,24 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw")
end
- def blob_raw_path
+ def blob_raw_url(only_path: false)
if @build && @entry
- raw_project_job_artifacts_path(@project, @build, path: @entry.path)
+ raw_project_job_artifacts_url(@project, @build, path: @entry.path, only_path: only_path)
elsif @snippet
if @snippet.project_id
- raw_project_snippet_path(@project, @snippet)
+ raw_project_snippet_url(@project, @snippet, only_path: only_path)
else
- raw_snippet_path(@snippet)
+ raw_snippet_url(@snippet, only_path: only_path)
end
elsif @blob
- project_raw_path(@project, @id)
+ project_raw_url(@project, @id, only_path: only_path)
end
end
+ def blob_raw_path
+ blob_raw_url(only_path: true)
+ end
+
# SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index aa3a9a055a0..4ec63fdaffc 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,7 @@ module BuildsHelper
def javascript_build_options
{
- page_url: project_job_url(@project, @build),
- build_url: project_job_url(@project, @build, :json),
+ page_path: project_job_path(@project, @build),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index e82136f0177..1ce487e6592 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -104,15 +104,23 @@ module DiffHelper
].join(' ').html_safe
end
- def diff_file_blob_raw_path(diff_file)
- project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
+ def diff_file_blob_raw_url(diff_file, only_path: false)
+ project_raw_url(@project, tree_join(diff_file.content_sha, diff_file.file_path), only_path: only_path)
end
- def diff_file_old_blob_raw_path(diff_file)
+ def diff_file_old_blob_raw_url(diff_file, only_path: false)
sha = diff_file.old_content_sha
return unless sha
- project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
+ project_raw_url(@project, tree_join(diff_file.old_content_sha, diff_file.old_path), only_path: only_path)
+ end
+
+ def diff_file_blob_raw_path(diff_file)
+ diff_file_blob_raw_url(diff_file, only_path: true)
+ end
+
+ def diff_file_old_blob_raw_path(diff_file)
+ diff_file_old_blob_raw_url(diff_file, only_path: true)
end
def diff_file_html_data(project, diff_file_path, diff_commit_id)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 212cdbb8157..0f110bd25c5 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -74,7 +74,7 @@ module IssuesHelper
elsif item.try(:merged?)
'status-box-merged'
elsif item.closed?
- 'status-box-closed'
+ 'status-box-mr-closed'
elsif item.try(:upcoming?)
'status-box-upcoming'
else
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e1ba7898ee6..c1c19062c91 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,6 +1,13 @@
module LabelsHelper
include ActionView::Helpers::TagHelper
+ def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
+ return true if label.is_a?(GroupLabel)
+ return true unless project
+
+ project.feature_available?(issuables_type, current_user)
+ end
+
# Link to a Label
#
# label - Label object to link to
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index 41d471cc92f..a3129cac2b1 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -1,11 +1,4 @@
module MembersHelper
- # Returns a `<action>_<source>_member` association, e.g.:
- # - admin_project_member, update_project_member, destroy_project_member
- # - admin_group_member, update_group_member, destroy_group_member
- def action_member_permission(action, member)
- "#{action}_#{member.type.underscore}".to_sym
- end
-
def remove_member_message(member, user: nil)
user = current_user if defined?(current_user)
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 8e822ed0ea2..aaee6eaeedd 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -58,7 +58,7 @@ module PreferencesHelper
user_view
elsif user_view == "activity"
"activity"
- elsif @project.wiki_enabled?
+ elsif can?(current_user, :read_wiki, @project)
"wiki"
elsif @project.feature_available?(:issues, current_user)
"projects/issues/issues"
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
index 4d2180f7eee..b76c1228220 100644
--- a/app/helpers/storage_health_helper.rb
+++ b/app/helpers/storage_health_helper.rb
@@ -18,16 +18,12 @@ module StorageHealthHelper
current_failures = circuit_breaker.failure_count
translation_params = { number_of_failures: current_failures,
- maximum_failures: maximum_failures,
- number_of_seconds: circuit_breaker.failure_wait_time }
+ maximum_failures: maximum_failures }
if circuit_breaker.circuit_broken?
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
"retry automatically. Reset storage information when the problem is "\
"resolved.") % translation_params
- elsif circuit_breaker.backing_off?
- _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
- "block access for %{number_of_seconds} seconds.") % translation_params
else
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"allow access on the next attempt.") % translation_params
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 77a82b895ce..50e17fe7717 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -5,7 +5,7 @@ module Emails
@commit = @note.noteable
@target_url = project_commit_url(*note_target_url_options)
- mail_answer_thread(@commit, note_thread_options(recipient_id))
+ mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id))
end
def note_issue_email(recipient_id, note_id)
@@ -13,7 +13,7 @@ module Emails
@issue = @note.noteable
@target_url = project_issue_url(*note_target_url_options)
- mail_answer_thread(@issue, note_thread_options(recipient_id))
+ mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id))
end
def note_merge_request_email(recipient_id, note_id)
@@ -21,7 +21,7 @@ module Emails
@merge_request = @note.noteable
@target_url = project_merge_request_url(*note_target_url_options)
- mail_answer_thread(@merge_request, note_thread_options(recipient_id))
+ mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
end
def note_snippet_email(recipient_id, note_id)
@@ -29,7 +29,7 @@ module Emails
@snippet = @note.noteable
@target_url = project_snippet_url(*note_target_url_options)
- mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
end
def note_personal_snippet_email(recipient_id, note_id)
@@ -37,7 +37,7 @@ module Emails
@snippet = @note.noteable
@target_url = snippet_url(@note.noteable)
- mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
end
private
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9efabe3f44e..ec886e993c3 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -119,8 +119,8 @@ class Notify < BaseMailer
headers['Reply-To'] = address
fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze
- headers['References'] ||= ''
- headers['References'] << ' ' << fallback_reply_message_id
+ headers['References'] ||= []
+ headers['References'] << fallback_reply_message_id
@reply_by_email = true
end
@@ -156,6 +156,18 @@ class Notify < BaseMailer
mail_thread(model, headers)
end
+ def mail_answer_note_thread(model, note, headers = {})
+ headers['Message-ID'] = message_id(note)
+ headers['In-Reply-To'] = message_id(note.references.last)
+ headers['References'] = note.references.map { |ref| message_id(ref) }
+
+ headers['X-GitLab-Discussion-ID'] = note.discussion.id if note.part_of_discussion?
+
+ headers[:subject]&.prepend('Re: ')
+
+ mail_thread(model, headers)
+ end
+
def reply_key
@reply_key ||= SentNotification.reply_key
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 3117c98c846..253e213af81 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -153,11 +153,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
- validates :circuitbreaker_backoff_threshold,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_wait_time,
+ validates :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout,
+ :circuitbreaker_check_interval,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -165,13 +164,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
- validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
- if value.to_i >= record.circuitbreaker_failure_count_threshold
- record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
- "lower than the failure count threshold"))
- end
- end
-
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 29e762724e3..19ad110db58 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -77,9 +77,15 @@ class Blob < SimpleDelegator
end
def self.lazy(project, commit_id, path)
- BatchLoader.for(commit_id: commit_id, path: path).batch do |items, loader|
- project.repository.blobs_at(items.map(&:values)).each do |blob|
- loader.call({ commit_id: blob.commit_id, path: blob.path }, blob) if blob
+ BatchLoader.for({ project: project, commit_id: commit_id, path: path }).batch do |items, loader|
+ items_by_project = items.group_by { |i| i[:project] }
+
+ items_by_project.each do |project, items|
+ items = items.map { |i| i.values_at(:commit_id, :path) }
+
+ project.repository.blobs_at(items).each do |blob|
+ loader.call({ project: blob.project, commit_id: blob.commit_id, path: blob.path }, blob) if blob
+ end
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 85960f1b6bb..83fe23606d1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -491,7 +491,6 @@ module Ci
end
def valid_dependency?
- return false unless complete?
return false if artifacts_expired?
return false if erased?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index eebbf7c4218..28f154581a9 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -228,6 +228,10 @@ module Ci
statuses.select(:stage).distinct.count
end
+ def total_size
+ statuses.count(:id)
+ end
+
def stages_names
statuses.order(:stage_idx).distinct
.pluck(:stage, :stage_idx).map(&:first)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 307e4fcedfe..13c31111134 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -52,6 +52,20 @@ class Commit
diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
end
+ def order_by(collection:, order_by:, sort:)
+ return collection unless %w[email name commits].include?(order_by)
+ return collection unless %w[asc desc].include?(sort)
+
+ collection.sort do |a, b|
+ operands = [a, b].tap { |o| o.reverse! if sort == 'desc' }
+
+ attr1, attr2 = operands.first.public_send(order_by), operands.second.public_send(order_by) # rubocop:disable PublicSend
+
+ # use case insensitive comparison for string values
+ order_by.in?(%w[email name]) ? attr1.casecmp(attr2) : attr1 <=> attr2
+ end
+ end
+
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..MIN_SHA_LENGTH]
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 98776eab424..90ad644ce34 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -85,8 +85,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
- cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
- return false unless cached
+ return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index b43eaeaeea0..c013e5a708f 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -44,13 +44,11 @@ module Mentionable
end
def all_references(current_user = nil, extractor: nil)
- @extractors ||= {}
-
# Use custom extractor if it's passed in the function parameters.
if extractor
- @extractors[current_user] = extractor
+ extractors[current_user] = extractor
else
- extractor = @extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user)
+ extractor = extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user)
extractor.reset_memoized_values
end
@@ -69,6 +67,10 @@ module Mentionable
extractor
end
+ def extractors
+ @extractors ||= {}
+ end
+
def mentioned_users(current_user = nil)
all_references(current_user).users
end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 7026f565706..fd6703831e4 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -103,9 +103,11 @@ module Milestoneish
end
def memoize_per_user(user, method_name)
- @memoized ||= {}
- @memoized[method_name] ||= {}
- @memoized[method_name][user&.id] ||= yield
+ memoized_users[method_name][user&.id] ||= yield
+ end
+
+ def memoized_users
+ @memoized_users ||= Hash.new { |h, k| h[k] = {} }
end
# override in a class that includes this module to get a faster query
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 5d75b2aa6a3..86f28f30032 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -46,6 +46,7 @@ module Noteable
notes.inc_relations_for_view.grouped_diff_discussions(*args)
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def resolvable_discussions
@resolvable_discussions ||=
if defined?(@discussions)
@@ -54,6 +55,7 @@ module Noteable
discussion_notes.resolvable.discussions(self)
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def discussions_resolvable?
resolvable_discussions.any?(&:resolvable?)
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index ce69fd34ac5..e48bc0be410 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -56,15 +56,17 @@ module Participable
#
# Returns an Array of User instances.
def participants(current_user = nil)
- @participants ||= Hash.new do |hash, user|
- hash[user] = raw_participants(user)
- end
-
- @participants[current_user]
+ all_participants[current_user]
end
private
+ def all_participants
+ @all_participants ||= Hash.new do |hash, user|
+ hash[user] = raw_participants(user)
+ end
+ end
+
def raw_participants(current_user = nil)
current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user)
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index e961c97e337..835f26aa57b 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -52,7 +52,7 @@ module RelativePositioning
# to its predecessor. This process will recursively move all the predecessors until we have a place
if (after.relative_position - before.relative_position) < 2
before.move_before
- @positionable_neighbours = [before]
+ @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
self.relative_position = position_between(before.relative_position, after.relative_position)
@@ -65,7 +65,7 @@ module RelativePositioning
if before.shift_after?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after)
issue_to_move.move_after
- @positionable_neighbours = [issue_to_move]
+ @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_after = issue_to_move.relative_position
end
@@ -80,7 +80,7 @@ module RelativePositioning
if after.shift_before?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before)
issue_to_move.move_before
- @positionable_neighbours = [issue_to_move]
+ @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_before = issue_to_move.relative_position
end
@@ -132,6 +132,7 @@ module RelativePositioning
end
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def save_positionable_neighbours
return unless @positionable_neighbours
@@ -140,4 +141,5 @@ module RelativePositioning
status
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index f006a271327..b6c7b6735b9 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -31,15 +31,11 @@ module ResolvableDiscussion
end
def resolvable?
- return @resolvable if @resolvable.present?
-
- @resolvable = potentially_resolvable? && notes.any?(&:resolvable?)
+ @resolvable ||= potentially_resolvable? && notes.any?(&:resolvable?)
end
def resolved?
- return @resolved if @resolved.present?
-
- @resolved = resolvable? && notes.none?(&:to_be_resolved?)
+ @resolved ||= resolvable? && notes.none?(&:to_be_resolved?)
end
def first_note
@@ -49,13 +45,13 @@ module ResolvableDiscussion
def first_note_to_resolve
return unless resolvable?
- @first_note_to_resolve ||= notes.find(&:to_be_resolved?)
+ @first_note_to_resolve ||= notes.find(&:to_be_resolved?) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def last_resolved_note
return unless resolved?
- @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last
+ @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def resolved_notes
@@ -95,7 +91,7 @@ module ResolvableDiscussion
yield(notes_relation)
# Set the notes array to the updated notes
- @notes = notes_relation.fresh.to_a
+ @notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
self.class.memoized_values.each do |var|
instance_variable_set(:"@#{var}", nil)
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 22fde2eb134..5c1cce98ad4 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -88,7 +88,7 @@ module Routable
def full_name
if route && route.name.present?
- @full_name ||= route.name
+ @full_name ||= route.name # rubocop:disable Gitlab/ModuleWithInstanceVariables
else
update_route if persisted?
@@ -112,7 +112,7 @@ module Routable
def expires_full_path_cache
RequestStore.delete(full_path_key) if RequestStore.active?
- @full_path = nil
+ @full_path = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def build_full_path
@@ -127,7 +127,7 @@ module Routable
def uncached_full_path
if route && route.path.present?
- @full_path ||= route.path
+ @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
else
update_route if persisted?
@@ -166,7 +166,7 @@ module Routable
route || build_route(source: self)
route.path = build_full_path
route.name = build_full_name
- @full_path = nil
- @full_name = nil
+ @full_path = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @full_name = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 731d9b9a745..5e4274619c4 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -12,6 +12,7 @@ module Spammable
attr_accessor :spam
attr_accessor :spam_log
+ alias_method :spam?, :spam
after_validation :check_for_spam, on: [:create, :update]
@@ -34,10 +35,6 @@ module Spammable
end
end
- def spam?
- @spam
- end
-
def check_for_spam
error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 25e2d8ea24e..d07041c2fdf 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -39,7 +39,7 @@ module Taskable
def task_list_items
return [] if description.blank?
- @task_list_items ||= Taskable.get_tasks(description)
+ @task_list_items ||= Taskable.get_tasks(description) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def tasks
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
index 9f403d96ed5..89fe6527647 100644
--- a/app/models/concerns/time_trackable.rb
+++ b/app/models/concerns/time_trackable.rb
@@ -21,6 +21,7 @@ module TimeTrackable
has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def spend_time(options)
@time_spent = options[:duration]
@time_spent_user = options[:user]
@@ -36,6 +37,7 @@ module TimeTrackable
end
end
alias_method :spend_time=, :spend_time
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def total_time_spent
timelogs.sum(:time_spent)
@@ -52,9 +54,10 @@ module TimeTrackable
private
def reset_spent_time
- timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user)
+ timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def add_or_subtract_spent_time
timelogs.new(
time_spent: time_spent,
@@ -62,16 +65,19 @@ module TimeTrackable
spent_at: @spent_at
)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def check_negative_time_spent
return if time_spent.nil? || time_spent == :reset
- # we need to cache the total time spent so multiple calls to #valid?
- # doesn't give a false error
- @original_total_time_spent ||= total_time_spent
-
- if time_spent < 0 && (time_spent.abs > @original_total_time_spent)
+ if time_spent < 0 && (time_spent.abs > original_total_time_spent)
errors.add(:time_spent, 'Time to subtract exceeds the total time spent')
end
end
+
+ # we need to cache the total time spent so multiple calls to #valid?
+ # doesn't give a false error
+ def original_total_time_spent
+ @original_total_time_spent ||= total_time_spent
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 0997b056c6a..6053594fab5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
includes(:author, :project, project: :namespace)
- .preload(:target, :push_event_payload)
+ .preload(:push_event_payload, target: :author)
end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ff811e19f8a..99d99bc6deb 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -14,11 +14,11 @@ class Identity < ActiveRecord::Base
end
def ldap?
- provider.starts_with?('ldap')
+ Gitlab::OAuth::Provider.ldap_provider?(provider)
end
def self.normalize_uid(provider, uid)
- if provider.to_s.starts_with?('ldap')
+ if Gitlab::OAuth::Provider.ldap_provider?(provider)
Gitlab::LDAP::Person.normalize_dn(uid)
else
uid.to_s
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 33db197e612..dc64888b6fc 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -10,6 +10,9 @@ class Issue < ActiveRecord::Base
include RelativePositioning
include TimeTrackable
include ThrottledTouch
+ include IgnorableColumn
+
+ ignore_column :assignee_id, :branch_name
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/member.rb b/app/models/member.rb
index 2fe5fda985f..c47145667b5 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base
include Importable
include Expirable
include Gitlab::Access
+ include Presentable
attr_accessor :raw_invite_token
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 422f138c4ea..c39789b047d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include ManualInverseAssociation
include EachBatch
include ThrottledTouch
+ include Gitlab::Utils::StrongMemoize
ignore_column :locked_at,
:ref_fetched
@@ -52,6 +53,7 @@ class MergeRequest < ActiveRecord::Base
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
after_create :ensure_merge_request_diff, unless: :importing?
+ after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
# When this attribute is true some MR validation is ignored
@@ -83,6 +85,14 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened
end
+ before_transition any => :opened do |merge_request|
+ merge_request.merge_jid = nil
+
+ merge_request.run_after_commit do
+ UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
+ end
+ end
+
state :opened
state :closed
state :merged
@@ -387,13 +397,17 @@ class MergeRequest < ActiveRecord::Base
end
def source_branch_head
- return unless source_project
-
- source_project.repository.commit(source_branch_ref) if source_branch_ref
+ strong_memoize(:source_branch_head) do
+ if source_project && source_branch_ref
+ source_project.repository.commit(source_branch_ref)
+ end
+ end
end
def target_branch_head
- target_project.repository.commit(target_branch_ref)
+ strong_memoize(:target_branch_head) do
+ target_project.repository.commit(target_branch_ref)
+ end
end
def branch_merge_base_commit
@@ -525,6 +539,13 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def clear_memoized_shas
+ @target_branch_sha = @source_branch_sha = nil
+
+ clear_memoization(:source_branch_head)
+ clear_memoization(:target_branch_head)
+ end
+
def reload_diff_if_branch_changed
if (source_branch_changed? || target_branch_changed?) &&
(source_branch_head && target_branch_head)
@@ -866,11 +887,11 @@ class MergeRequest < ActiveRecord::Base
def state_icon_name
if merged?
- "check"
+ "git-merge"
elsif closed?
- "times"
+ "close"
else
- "circle-o"
+ "issue-open-m"
end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index c37aa0a594b..e35de9b97ee 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base
def base_commit
return unless base_commit_sha
- project.commit(base_commit_sha)
+ project.commit_by(oid: base_commit_sha)
end
def start_commit
return unless start_commit_sha
- project.commit(start_commit_sha)
+ project.commit_by(oid: start_commit_sha)
end
def head_commit
return unless head_commit_sha
- project.commit(head_commit_sha)
+ project.commit_by(oid: head_commit_sha)
end
def commit_shas
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 901dbf2ba69..0ff169d4531 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -40,6 +40,7 @@ class Namespace < ActiveRecord::Base
namespace_path: true
validate :nesting_level_allowed
+ validate :allowed_path_by_redirects
delegate :name, to: :owner, allow_nil: true, prefix: true
@@ -257,4 +258,14 @@ class Namespace < ActiveRecord::Base
Namespace.where(id: descendants.select(:id))
.update_all(share_with_group_lock: true)
end
+
+ def allowed_path_by_redirects
+ return if path.nil?
+
+ errors.add(:path, "#{path} has been taken before. Please use another one") if namespace_previously_created_with_same_path?
+ end
+
+ def namespace_previously_created_with_same_path?
+ RedirectRoute.permanent.exists?(path: path)
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index c4c2ab8e67d..184fbd5f5ae 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -360,6 +360,16 @@ class Note < ActiveRecord::Base
end
end
+ def references
+ refs = [noteable]
+
+ if part_of_discussion?
+ refs += discussion.notes.take_while { |n| n.id < id }
+ end
+
+ refs
+ end
+
def expire_etag_cache
return unless noteable&.discussions_rendered_on_frontend?
@@ -401,6 +411,9 @@ class Note < ActiveRecord::Base
end
noteable_object&.touch
+
+ # We return the noteable object so we can re-use it in EE for ElasticSearch.
+ noteable_object
end
def banzai_render_context(field)
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index cfcb03138b7..063dc521324 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,6 +3,8 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
+ REDIS_EXPIRY_TIME = 3.minutes
+
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user
@@ -27,6 +29,21 @@ class PersonalAccessToken < ActiveRecord::Base
!revoked? && !expired?
end
+ def self.redis_getdel(user_id)
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(redis_shared_state_key(user_id))
+ redis.del(redis_shared_state_key(user_id))
+ token
+ end
+ end
+
+ def self.redis_store!(user_id, token)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_shared_state_key(user_id), token, ex: REDIS_EXPIRY_TIME)
+ token
+ end
+ end
+
protected
def validate_scopes
@@ -38,4 +55,8 @@ class PersonalAccessToken < ActiveRecord::Base
def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
+
+ def self.redis_shared_state_key(user_id)
+ "gitlab:personal_access_token:#{user_id}"
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 6ae15a0a50f..5183a216c53 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base
end
def import_started?
- import? && import_status == 'started'
+ # import? does SQL work so only run it if it looks like there's an import running
+ import_status == 'started' && import?
end
def import_scheduled?
@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
- repository.write_ref('HEAD', "refs/heads/#{branch}")
+ repository.write_ref('HEAD', "refs/heads/#{branch}", force: true)
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1c065e1ddbd..2be35b6ea9d 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -46,6 +46,8 @@ class JiraService < IssueTrackerService
context_path: url.path,
auth_type: :basic,
read_timeout: 120,
+ use_cookies: true,
+ additional_cookies: ['OBBasicAuth=fromDialog'],
use_ssl: url.scheme == 'https'
}
end
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 31de204d824..20532527346 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -17,4 +17,32 @@ class RedirectRoute < ActiveRecord::Base
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
+
+ scope :permanent, -> do
+ if column_permanent_exists?
+ where(permanent: true)
+ else
+ none
+ end
+ end
+
+ scope :temporary, -> do
+ if column_permanent_exists?
+ where(permanent: [false, nil])
+ else
+ all
+ end
+ end
+
+ default_value_for :permanent, false
+
+ def permanent=(value)
+ if self.class.column_permanent_exists?
+ super
+ end
+ end
+
+ def self.column_permanent_exists?
+ ActiveRecord::Base.connection.column_exists?(:redirect_routes, :permanent)
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 751306188a0..552a354d1ce 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -19,6 +19,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
+ delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError)
@@ -237,11 +238,10 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
- write_ref(keep_around_ref_name(sha), sha)
- rescue Rugged::ReferenceError => ex
- Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
- rescue Rugged::OSError => ex
- raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+ write_ref(keep_around_ref_name(sha), sha, force: true)
+ rescue Gitlab::Git::Repository::GitError => ex
+ # Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156
+ return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end
@@ -251,10 +251,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
- def write_ref(ref_path, sha)
- rugged.references.create(ref_path, sha, force: true)
- end
-
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
@@ -690,7 +686,9 @@ class Repository
def tags_sorted_by(value)
case value
- when 'name'
+ when 'name_asc'
+ VersionSorter.sort(tags) { |tag| tag.name }
+ when 'name_desc'
VersionSorter.rsort(tags) { |tag| tag.name }
when 'updated_desc'
tags_sorted_by_committed_date.reverse
@@ -701,10 +699,14 @@ class Repository
end
end
- def contributors
+ # Params:
+ #
+ # order_by: name|email|commits
+ # sort: asc|desc default: 'asc'
+ def contributors(order_by: nil, sort: 'asc')
commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
- commits.group_by(&:author_email).map do |email, commits|
+ commits = commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
contributor.email = email
@@ -718,6 +720,7 @@ class Repository
contributor
end
+ Commit.order_by(collection: commits, order_by: order_by, sort: sort)
end
def refs_contains_sha(ref_type, sha)
@@ -931,7 +934,7 @@ class Repository
def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
- rugged.merge_base(first_commit_id, second_commit_id)
+ raw_repository.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError
nil
end
@@ -971,8 +974,7 @@ class Repository
tmp_remote_name = true
end
- add_remote(remote_name, url)
- set_remote_as_mirror(remote_name, refmap: refmap)
+ add_remote(remote_name, url, mirror_refmap: refmap)
fetch_remote(remote_name, forced: forced)
ensure
remove_remote(remote_name) if tmp_remote_name
@@ -995,7 +997,7 @@ class Repository
end
def create_ref(ref, ref_path)
- raw_repository.write_ref(ref_path, ref)
+ write_ref(ref_path, ref)
end
def ls_files(ref)
diff --git a/app/models/route.rb b/app/models/route.rb
index 97e8a6ad9e9..7ba3ec06041 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,6 +8,8 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
+ validate :ensure_permanent_paths
+
after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
after_update :create_redirect_for_old_path
@@ -40,7 +42,7 @@ class Route < ActiveRecord::Base
# We are not calling route.delete_conflicting_redirects here, in hopes
# of avoiding deadlocks. The parent (self, in this method) already
# called it, which deletes conflicts for all descendants.
- route.create_redirect(old_path) if attributes[:path]
+ route.create_redirect(old_path, permanent: permanent_redirect?) if attributes[:path]
end
end
end
@@ -50,16 +52,30 @@ class Route < ActiveRecord::Base
end
def conflicting_redirects
- RedirectRoute.matching_path_and_descendants(path)
+ RedirectRoute.temporary.matching_path_and_descendants(path)
end
- def create_redirect(path)
- RedirectRoute.create(source: source, path: path)
+ def create_redirect(path, permanent: false)
+ RedirectRoute.create(source: source, path: path, permanent: permanent)
end
private
def create_redirect_for_old_path
- create_redirect(path_was) if path_changed?
+ create_redirect(path_was, permanent: permanent_redirect?) if path_changed?
+ end
+
+ def permanent_redirect?
+ source_type != "Project"
+ end
+
+ def ensure_permanent_paths
+ return if path.nil?
+
+ errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists?
+ end
+
+ def conflicting_redirect_exists?
+ RedirectRoute.permanent.matching_path_and_descendants(path).exists?
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index af1c36d9c93..51941f43919 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -315,6 +315,8 @@ class User < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
+ query = query.downcase
+
order = <<~SQL
CASE
WHEN users.name = %{query} THEN 0
@@ -324,8 +326,11 @@ class User < ActiveRecord::Base
END
SQL
- fuzzy_search(query, [:name, :email, :username])
- .reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
+ where(
+ fuzzy_arel_match(:name, query)
+ .or(fuzzy_arel_match(:username, query))
+ .or(arel_table[:email].eq(query))
+ ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
# searches user by given pattern
@@ -333,15 +338,17 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query)
+ query = query.downcase
+
email_table = Email.arel_table
matched_by_emails_user_ids = email_table
.project(email_table[:user_id])
- .where(Email.fuzzy_arel_match(:email, query))
+ .where(email_table[:email].eq(query))
where(
fuzzy_arel_match(:name, query)
- .or(fuzzy_arel_match(:email, query))
.or(fuzzy_arel_match(:username, query))
+ .or(arel_table[:email].eq(query))
.or(arel_table[:id].in(matched_by_emails_user_ids))
)
end
@@ -731,7 +738,7 @@ class User < ActiveRecord::Base
def ldap_user?
if identities.loaded?
- identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
+ identities.find { |identity| Gitlab::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
@@ -1054,13 +1061,13 @@ class User < ActiveRecord::Base
end
def todos_done_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :done).execute.count
end
end
def todos_pending_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :pending).execute.count
end
end
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 9f374304164..548b99b69d9 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -6,11 +6,11 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
SYNCABLE_ATTRIBUTES = %i[name email location].freeze
def read_only?(attribute)
- Gitlab.config.omniauth.sync_profile_from_provider && synced?(attribute)
+ sync_profile_from_provider? && synced?(attribute)
end
def read_only_attributes
- return [] unless Gitlab.config.omniauth.sync_profile_from_provider
+ return [] unless sync_profile_from_provider?
SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
end
@@ -22,4 +22,10 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
def set_attribute_synced(attribute, value)
write_attribute("#{attribute}_synced", value)
end
+
+ private
+
+ def sync_profile_from_provider?
+ Gitlab::OAuth::Provider.sync_profile_from_provider?(provider)
+ end
end
diff --git a/app/presenters/group_member_presenter.rb b/app/presenters/group_member_presenter.rb
new file mode 100644
index 00000000000..8f53dfa105e
--- /dev/null
+++ b/app/presenters/group_member_presenter.rb
@@ -0,0 +1,15 @@
+class GroupMemberPresenter < MemberPresenter
+ private
+
+ def admin_member_permission
+ :admin_group_member
+ end
+
+ def update_member_permission
+ :update_group_member
+ end
+
+ def destroy_member_permission
+ :destroy_group_member
+ end
+end
diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb
new file mode 100644
index 00000000000..7d2f9303b8f
--- /dev/null
+++ b/app/presenters/member_presenter.rb
@@ -0,0 +1,38 @@
+class MemberPresenter < Gitlab::View::Presenter::Delegated
+ presents :member
+
+ def access_level_roles
+ member.class.access_level_roles
+ end
+
+ def can_resend_invite?
+ invite? &&
+ can?(current_user, admin_member_permission, source)
+ end
+
+ def can_update?
+ can?(current_user, update_member_permission, member)
+ end
+
+ def can_remove?
+ can?(current_user, destroy_member_permission, member)
+ end
+
+ def can_approve?
+ request? && can_update?
+ end
+
+ private
+
+ def admin_member_permission
+ raise NotImplementedError
+ end
+
+ def update_member_permission
+ raise NotImplementedError
+ end
+
+ def destroy_member_permission
+ raise NotImplementedError
+ end
+end
diff --git a/app/presenters/members_presenter.rb b/app/presenters/members_presenter.rb
new file mode 100644
index 00000000000..e4aba37b69e
--- /dev/null
+++ b/app/presenters/members_presenter.rb
@@ -0,0 +1,15 @@
+class MembersPresenter < Gitlab::View::Presenter::Delegated
+ include Enumerable
+
+ presents :members
+
+ def to_ary
+ to_a
+ end
+
+ def each
+ members.each do |member|
+ yield member.present(current_user: current_user)
+ end
+ end
+end
diff --git a/app/presenters/project_member_presenter.rb b/app/presenters/project_member_presenter.rb
new file mode 100644
index 00000000000..7f42d2b70df
--- /dev/null
+++ b/app/presenters/project_member_presenter.rb
@@ -0,0 +1,15 @@
+class ProjectMemberPresenter < MemberPresenter
+ private
+
+ def admin_member_permission
+ :admin_project_member
+ end
+
+ def update_member_permission
+ :update_project_member
+ end
+
+ def destroy_member_permission
+ :destroy_project_member
+ end
+end
diff --git a/app/serializers/concerns/with_pagination.rb b/app/serializers/concerns/with_pagination.rb
index d29e22d6740..89631b73fcf 100644
--- a/app/serializers/concerns/with_pagination.rb
+++ b/app/serializers/concerns/with_pagination.rb
@@ -14,7 +14,7 @@ module WithPagination
# we shouldn't try to paginate single resources
def represent(resource, opts = {})
if paginated? && resource.respond_to?(:page)
- super(@paginator.paginate(resource), opts)
+ super(paginator.paginate(resource), opts)
else
super(resource, opts)
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 1e5f2ed4dd2..c8b112132b3 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -12,18 +12,19 @@ module Ci
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new
- command = OpenStruct.new(source: source,
- origin_ref: params[:ref],
- checkout_sha: params[:checkout_sha],
- after_sha: params[:after],
- before_sha: params[:before],
- trigger_request: trigger_request,
- schedule: schedule,
- ignore_skip_ci: ignore_skip_ci,
- save_incompleted: save_on_errors,
- seeds_block: block,
- project: project,
- current_user: current_user)
+ command = Gitlab::Ci::Pipeline::Chain::Command.new(
+ source: source,
+ origin_ref: params[:ref],
+ checkout_sha: params[:checkout_sha],
+ after_sha: params[:after],
+ before_sha: params[:before],
+ trigger_request: trigger_request,
+ schedule: schedule,
+ ignore_skip_ci: ignore_skip_ci,
+ save_incompleted: save_on_errors,
+ seeds_block: block,
+ project: project,
+ current_user: current_user)
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
.new(pipeline, command, SEQUENCE)
@@ -80,7 +81,7 @@ module Ci
end
def related_merge_requests
- MergeRequest.where(source_project: pipeline.project, source_branch: pipeline.ref)
+ MergeRequest.opened.where(source_project: pipeline.project, source_branch: pipeline.ref)
end
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index c8b6450c9b5..f832b79ef21 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -38,11 +38,15 @@ module Ci
begin
# In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
- build.runner_id = runner.id
- build.run!
- register_success(build)
-
- return Result.new(build, true)
+ begin
+ build.runner_id = runner.id
+ build.run!
+ register_success(build)
+
+ return Result.new(build, true)
+ rescue Ci::Build::MissingDependenciesError
+ build.drop!(:missing_dependency_failure)
+ end
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
# We are looping to find another build that is not conflicting
# It also indicates that this build can be picked and passed to runner.
@@ -54,9 +58,6 @@ module Ci
# we still have to return 409 in the end,
# to make sure that this is properly handled by runner.
valid = false
- rescue Ci::Build::MissingDependenciesError
- build.drop!(:missing_dependency_failure)
- valid = false
end
end
diff --git a/app/services/concerns/issues/resolve_discussions.rb b/app/services/concerns/issues/resolve_discussions.rb
index 7d45b4aa26a..26eb274f4d5 100644
--- a/app/services/concerns/issues/resolve_discussions.rb
+++ b/app/services/concerns/issues/resolve_discussions.rb
@@ -1,24 +1,28 @@
module Issues
module ResolveDiscussions
+ include Gitlab::Utils::StrongMemoize
+
attr_reader :merge_request_to_resolve_discussions_of_iid, :discussion_to_resolve_id
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_resolve_discussion_params
@merge_request_to_resolve_discussions_of_iid ||= params.delete(:merge_request_to_resolve_discussions_of)
@discussion_to_resolve_id ||= params.delete(:discussion_to_resolve)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def merge_request_to_resolve_discussions_of
- return @merge_request_to_resolve_discussions_of if defined?(@merge_request_to_resolve_discussions_of)
-
- @merge_request_to_resolve_discussions_of = MergeRequestsFinder.new(current_user, project_id: project.id)
- .execute
- .find_by(iid: merge_request_to_resolve_discussions_of_iid)
+ strong_memoize(:merge_request_to_resolve_discussions_of) do
+ MergeRequestsFinder.new(current_user, project_id: project.id)
+ .execute
+ .find_by(iid: merge_request_to_resolve_discussions_of_iid)
+ end
end
def discussions_to_resolve
return [] unless merge_request_to_resolve_discussions_of
- @discussions_to_resolve ||=
+ @discussions_to_resolve ||= # rubocop:disable Gitlab/ModuleWithInstanceVariables
if discussion_to_resolve_id
discussion_or_nil = merge_request_to_resolve_discussions_of
.find_discussion(discussion_to_resolve_id)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2c51ac13815..e7463e6e25c 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -106,12 +106,14 @@ class IssuableBaseService < BaseService
end
def merge_quick_actions_into_params!(issuable)
+ original_description = params.fetch(:description, issuable.description)
+
description, command_params =
QuickActions::InterpretService.new(project, current_user)
- .execute(params[:description], issuable)
+ .execute(original_description, issuable)
# Avoid a description already set on an issuable to be overwritten by a nil
- params[:description] = description if params.key?(:description)
+ params[:description] = description if description
params.merge!(command_params)
end
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
index c13f289f61e..2a2bb0cae5b 100644
--- a/app/services/members/approve_access_request_service.rb
+++ b/app/services/members/approve_access_request_service.rb
@@ -35,8 +35,17 @@ module Members
def can_update_access_requester?(access_requester, opts = {})
access_requester && (
opts[:force] ||
- can?(current_user, action_member_permission(:update, access_requester), access_requester)
+ can?(current_user, update_member_permission(access_requester), access_requester)
)
end
+
+ def update_member_permission(member)
+ case member
+ when GroupMember
+ :update_group_member
+ when ProjectMember
+ :update_project_member
+ end
+ end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 46c505baf8b..05b93ac8fdb 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -36,7 +36,16 @@ module Members
end
def can_destroy_member?(member)
- member && can?(current_user, action_member_permission(:destroy, member), member)
+ member && can?(current_user, destroy_member_permission(member), member)
+ end
+
+ def destroy_member_permission(member)
+ case member
+ when GroupMember
+ :destroy_group_member
+ when ProjectMember
+ :destroy_project_member
+ end
end
end
end
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
index 11030bee8f1..d4ade869777 100644
--- a/app/services/spam_check_service.rb
+++ b/app/services/spam_check_service.rb
@@ -7,16 +7,19 @@
# - params with :request
#
module SpamCheckService
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_spam_check_params
@request = params.delete(:request)
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
# In order to be proceed to the spam check process, @spammable has to be
# a dirty instance, which means it should be already assigned with the new
# attribute values.
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
@@ -24,4 +27,5 @@ module SpamCheckService
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a9d0503bc73..3e2dbb07a6c 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -546,6 +546,12 @@
%fieldset
%legend Git Storage Circuitbreaker settings
.form-group
+ = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :circuitbreaker_check_interval, class: 'form-control'
+ .help-block
+ = circuitbreaker_check_interval_help_text
+ .form-group
= f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_access_retries, class: 'form-control'
@@ -558,18 +564,6 @@
.help-block
= circuitbreaker_storage_timeout_help_text
.form-group
- = f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
- .help-block
- = circuitbreaker_backoff_threshold_help_text
- .form-group
- = f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
- .help-block
- = circuitbreaker_failure_wait_time_help_text
- .form-group
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index 6bf979a937e..23f9927cfee 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -15,7 +15,7 @@
Unable to collect CPU info
.col-sm-4
.light-well
- %h4 Memory
+ %h4 Memory Usage
.data
- if @memory
%h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
@@ -24,7 +24,7 @@
Unable to collect memory info
.col-sm-4
.light-well
- %h4 Disks
+ %h4 Disk Usage
.data
- @disks.each do |disk|
%h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
@@ -34,4 +34,4 @@
.light-well
%h4 Uptime
.data
- %h1= time_ago_with_tooltip(Rails.application.config.booted_at)
+ %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 98ff592eb64..63c5a15de1c 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -157,7 +157,6 @@
%ul
%li User will not be able to login
%li User will not be able to access git repositories
- %li User will be removed from joined projects and groups
%li Personal projects will be left
%li Owned groups will be left
%br
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 2bac69bc536..6e399fc7392 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -10,5 +10,7 @@
%p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above.
- else
- = render "ci/variables/table"
- %button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
+ .js-secret-variable-table
+ = render "ci/variables/table"
+ %button.btn.btn-info.js-secret-value-reveal-button{ data: { secret_reveal_status: 'false' } }
+ = n_('Reveal value', 'Reveal values', @variables.size)
diff --git a/app/views/ci/variables/_table.html.haml b/app/views/ci/variables/_table.html.haml
index 71a0b56c4f4..2298930d0c7 100644
--- a/app/views/ci/variables/_table.html.haml
+++ b/app/views/ci/variables/_table.html.haml
@@ -15,7 +15,11 @@
- if variable.id?
%tr
%td.variable-key= variable.key
- %td.variable-value{ "data-value" => variable.value }******
+ %td.variable-value
+ %span.js-secret-value-placeholder
+ = '*' * 6
+ %span.hide.js-secret-value
+ = variable.value
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
%td.variable-menu
= link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index a5686002328..20ca6ec969a 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -83,12 +83,12 @@
You're all done!
- elsif current_user.todos.any?
.todos-all-done
- .svg-content
+ .svg-content.svg-250
= image_tag 'illustrations/todos_all_done.svg'
- if todos_filter_empty?
%h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample
- %p.text-center
+ %p
Are you looking for things to do? Take a look at
= succeed "," do
= link_to "the opened issues", issues_dashboard_path
@@ -104,7 +104,7 @@
= image_tag 'illustrations/todos_empty.svg'
.todos-empty-content
%h4
- Todos let you see what you should do next.
+ Todos let you see what you should do next
%p
When an issue or merge request is assigned to you, or when you
%strong
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 25ed610466a..eba9cd253bb 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,7 +1,7 @@
-.page-with-sidebar{ class: page_with_sidebar_class }
+.layout-page{ class: page_with_sidebar_class }
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
- .content-wrapper.page-with-new-nav
+ .content-wrapper
= render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
diff --git a/app/views/layouts/_recaptcha_verification.html.haml b/app/views/layouts/_recaptcha_verification.html.haml
index 77c77dc6754..e6f87ddd383 100644
--- a/app/views/layouts/_recaptcha_verification.html.haml
+++ b/app/views/layouts/_recaptcha_verification.html.haml
@@ -1,5 +1,4 @@
- humanized_resource_name = spammable.class.model_name.human.downcase
-- resource_name = spammable.class.model_name.singular
%h3.page-title
Anti-spam verification
@@ -8,16 +7,4 @@
%p
#{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
-= form_for form do |f|
- .recaptcha
- - params[resource_name].each do |field, value|
- = hidden_field(resource_name, field, value: value)
- = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
- = hidden_field_tag(:recaptcha_verification, true)
- = recaptcha_tags
-
- -# Yields a block with given extra params.
- = yield
-
- .row-content-block.footer-block
- = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
+= render 'shared/recaptcha_form', spammable: spammable
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 30ae385f62f..52587760ba4 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,14 @@
.location-badge= label
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
- = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
+ = search_field_tag 'search', nil, placeholder: 'Search',
+ class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
+ spellcheck: false,
+ tabindex: '1',
+ autocomplete: 'off',
+ data: { issues_path: issues_dashboard_path,
+ mr_path: merge_requests_dashboard_path },
+ aria: { label: 'Search' }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select
= dropdown_content do
diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml
index a7370180bf6..32a24c101fc 100644
--- a/app/views/layouts/nav/projects_dropdown/_show.html.haml
+++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml
@@ -1,4 +1,4 @@
-- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: @project.web_url, avatar_url: @project.avatar_url } if @project&.persisted?
+- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
.projects-dropdown-container
.project-dropdown-sidebar
%ul
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 0ec07605631..cb8db306b56 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 0bf318b0b66..0c27b09f7b1 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,7 +1,7 @@
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to group_path(@group), title: @group.name do
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index 7e23f9c1f05..a5a62a0695f 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to profile_path, title: 'Profile Settings' do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 53a9162b703..be39f577ba7 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
- can_edit = can?(current_user, :admin_project, @project)
.context-header
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 574a8f2fa50..bae37292d62 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -109,7 +109,7 @@
API
%tr
%td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" }
- - job_count = @pipeline.statuses.latest.size
+ - job_count = @pipeline.total_size
- stage_count = @pipeline.stages_count
successfully completed
#{job_count} #{'job'.pluralize(job_count)}
diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb
index ddced2279e1..39622cf7f02 100644
--- a/app/views/notify/pipeline_success_email.text.erb
+++ b/app/views/notify/pipeline_success_email.text.erb
@@ -22,11 +22,11 @@ Committed by: <%= commit.committer_name %>
<% end -%>
<% end -%>
-<% build_count = @pipeline.statuses.latest.size -%>
+<% job_count = @pipeline.total_size -%>
<% stage_count = @pipeline.stages_count -%>
<% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
-successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
+successfully completed <%= job_count %> <%= 'job'.pluralize(job_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 26c2e4c5936..f445e5a2417 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -15,14 +15,13 @@
They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
.col-lg-8
-
- - if flash[:personal_access_token]
+ - if @new_personal_access_token
.created-personal-access-token-container
%h5.prepend-top-0
Your New Personal Access Token
.form-group
- = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
- = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
+ = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
+ = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index 1dd8778f800..f6e5712ce81 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -8,7 +8,7 @@
%br
%span.descr
Pipelines need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')
+ = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank'
.checkbox
= form.label :only_allow_merge_if_all_discussions_are_resolved do
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml
index 98bedae650a..5d457a50c49 100644
--- a/app/views/projects/blob/_header_content.html.haml
+++ b/app/views/projects/blob/_header_content.html.haml
@@ -8,3 +8,6 @@
%small
= number_to_human_size(blob.raw_size)
+
+ - if blob.stored_externally? && blob.external_storage == :lfs
+ %span.label.label-lfs.append-right-5 LFS
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 2a178325041..5b092427496 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -3,15 +3,15 @@
Template
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- = dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } )
+ = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
+ = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
.template-selectors-undo-menu.hidden
%span.text-info Template applied
%button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/blob/viewers/_image.html.haml b/app/views/projects/blob/viewers/_image.html.haml
index 26ea028c5d7..2a8cefac005 100644
--- a/app/views/projects/blob/viewers/_image.html.haml
+++ b/app/views/projects/blob/viewers/_image.html.haml
@@ -1,2 +1,3 @@
.file-content.image_file
- = image_tag(blob_raw_path, alt: viewer.blob.name)
+ -# Uses the full URL rather than the path, to prevent it from getting prefixed with the asset host.
+ = image_tag(blob_raw_url, alt: viewer.blob.name)
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 2baaaf6ac5b..e9d8fc75142 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -20,7 +20,7 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index c1842527480..86510b8ab93 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to project_job_url(job.project, job) do
+ = link_to project_job_path(job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml
index 18ca01d2d49..ad696daa259 100644
--- a/app/views/projects/clusters/_cluster.html.haml
+++ b/app/views/projects/clusters/_cluster.html.haml
@@ -16,7 +16,8 @@
class: "js-toggle-cluster-list project-feature-toggle #{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"),
disabled: !cluster.can_toggle_cluster?,
- data: { "enabled-text": s_("ClusterIntegration|Active"),
- "disabled-text": s_("ClusterIntegration|Inactive"),
- endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
+ data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
= icon("spinner spin", class: "loading-icon")
+ %span.toggle-icon
+ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
+ = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index e629cc58b06..b525f4efc83 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -1,12 +1,12 @@
.row.empty-state
.col-xs-12
.svg-content= image_tag 'illustrations/clusters_empty.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- %h4= s_('ClusterIntegration|Integrate cluster automation')
+ %h4.text-center= s_('ClusterIntegration|Integrate cluster automation')
- link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
- %p
+ .text-center
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/_enabled.html.haml b/app/views/projects/clusters/_enabled.html.haml
index 70c677f7856..547b3c8446f 100644
--- a/app/views/projects/clusters/_enabled.html.haml
+++ b/app/views/projects/clusters/_enabled.html.haml
@@ -7,8 +7,10 @@
%button{ type: 'button',
class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"),
- disabled: !can?(current_user, :update_cluster, @cluster),
- data: { "enabled-text": s_("ClusterIntegration|Active"), "disabled-text": s_("ClusterIntegration|Inactive"), } }
+ disabled: !can?(current_user, :update_cluster, @cluster) }
+ %span.toggle-icon
+ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
+ = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
- if can?(current_user, :update_cluster, @cluster)
.form-group
diff --git a/app/views/projects/clusters/_tabs.html.haml b/app/views/projects/clusters/_tabs.html.haml
deleted file mode 100644
index c8120e806fa..00000000000
--- a/app/views/projects/clusters/_tabs.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.top-area.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left= icon("angle-left")
- .fade-right= icon("angle-right")
- %ul.nav-links.scrolling-tabs
- %li{ class: ('active' if @scope == 'active') }>
- = link_to project_clusters_path(@project, scope: :active), class: "js-active-tab" do
- = s_("ClusterIntegration|Active")
- %span.badge= @active_count
- %li{ class: ('active' if @scope == 'inactive') }>
- = link_to project_clusters_path(@project, scope: :inactive), class: "js-inactive-tab" do
- = s_("ClusterIntegration|Inactive")
- %span.badge= @inactive_count
- %li{ class: ('active' if @scope.nil? || @scope == 'all') }>
- = link_to project_clusters_path(@project), class: "js-all-tab" do
- = s_("ClusterIntegration|All")
- %span.badge= @all_count
diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml
index 104e39b0e06..bec512be91c 100644
--- a/app/views/projects/clusters/index.html.haml
+++ b/app/views/projects/clusters/index.html.haml
@@ -2,8 +2,12 @@
- page_title "Clusters"
.clusters-container
- - if !@clusters.empty?
- = render "tabs"
+ - if @clusters.empty?
+ = render "empty_state"
+ - else
+ .top-area.adjust
+ .nav-text
+ = s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project")
.ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" }
@@ -16,9 +20,3 @@
- @clusters.each do |cluster|
= render "cluster", cluster: cluster.present(current_user: current_user)
= paginate @clusters, theme: "gitlab"
- - elsif @scope == 'all'
- = render "empty_state"
- - else
- = render "tabs"
- .prepend-top-20.text-center
- = s_("ClusterIntegration|There are no clusters to show")
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 618a6355d23..d66066a6d0b 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -38,8 +38,8 @@
.commiter
- commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- - commit_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom')
- - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
+ - commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
+ - commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
.commit-actions.flex-row.hidden-xs
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index ef305120525..ab371521840 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -3,7 +3,7 @@
- page_title _("Commits"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
.js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class }
diff --git a/app/views/projects/diffs/_replaced_image_diff.html.haml b/app/views/projects/diffs/_replaced_image_diff.html.haml
index 8fc232b464e..6dffc7c4390 100644
--- a/app/views/projects/diffs/_replaced_image_diff.html.haml
+++ b/app/views/projects/diffs/_replaced_image_diff.html.haml
@@ -1,7 +1,7 @@
- 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)
+- blob_raw_url = diff_file_blob_raw_url(diff_file)
+- old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = ''
@@ -13,7 +13,7 @@
.two-up.view
.wrap
.frame.deleted
- = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
+ = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
%p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size)
|
@@ -23,7 +23,7 @@
%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 }
+ = 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_url, alt: diff_file.new_path }
%p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size)
|
@@ -36,9 +36,9 @@
.swipe.view.hide
.swipe-frame
.frame.deleted
- = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false)
+ = image_tag(old_blob_raw_url, 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 }
+ = 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_url, alt: diff_file.new_path }
%span.swipe-bar
%span.top-handle
%span.bottom-handle
@@ -46,8 +46,8 @@
.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 }
+ = image_tag(old_blob_raw_url, 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_url, alt: diff_file.new_path }
.controls
.transparent
.drag-track
diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml
index 6b0c6bbe48f..12be8beab39 100644
--- a/app/views/projects/diffs/_single_image_diff.html.haml
+++ b/app/views/projects/diffs/_single_image_diff.html.haml
@@ -1,7 +1,7 @@
- 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)
+- blob_raw_url = diff_file_blob_raw_url(diff_file)
+- old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = ''
@@ -12,5 +12,5 @@
.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 }
+ = 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_url, alt: diff_file.file_path }
%p.image-info= number_to_human_size(blob.size)
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index e0aedcac5e1..ad94113fffd 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -15,8 +15,10 @@
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
- "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started'),
- "empty-loading-svg-path": image_path('illustrations/monitoring/loading'),
- "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'),
+ "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
+ "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
+ "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+ "project-path": project_path(@project),
+ "tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 1eccc0509bd..9779c1985d5 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -14,4 +14,4 @@
notes_path: notes_url,
last_fetched_at: Time.now.to_i,
noteable_data: serialize_issuable(@issue),
- current_user_data: UserSerializer.new.represent(current_user).to_json } }
+ current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 2f7aece7440..eab7879c7bf 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -14,12 +14,12 @@
.detail-page-header
.detail-page-header-body
- .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
- = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ .issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) }
+ = sprite_icon('mobile-issue-close', size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs
Closed
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
- = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ = sprite_icon('issue-open-m', size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs Open
.issuable-meta
@@ -40,7 +40,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_issue
- %li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit'
+ %li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'js-issuable-edit'
- unless current_user == @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue
@@ -53,7 +53,7 @@
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue
- = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
+ = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped js-issuable-edit'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index 135f9ab0aff..22c8b6b513d 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -7,7 +7,7 @@
.detail-page-header
.detail-page-header-body
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
- = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
+ = sprite_icon(@merge_request.state_icon_name, size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs
= @merge_request.state_human_name
@@ -27,7 +27,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_merge_request
- %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+ %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- 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
@@ -37,6 +37,6 @@
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request
- = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
+ = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped js-issuable-edit"
= render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 4bb97ecdd16..2f56630c22e 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -86,7 +86,7 @@
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
- = link_to new_import_gitea_url, class: 'btn import_gitea' do
+ = link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index f5149306734..01ea9356af5 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -13,7 +13,7 @@
.well-segment.pipeline-info
.icon-container
= icon('clock-o')
- = pluralize @pipeline.statuses.count(:id), "job"
+ = pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
= link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index ad61f033a1c..398a1c46746 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -8,7 +8,7 @@
%li.js-builds-tab-link
= link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
Jobs
- %span.badge.js-builds-counter= pipeline.statuses.count
+ %span.badge.js-builds-counter= pipeline.total_size
- if failed_builds.present?
%li.js-failures-tab-link
= link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index c63e716180c..c5f9f5aa15b 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -40,10 +40,14 @@
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
%hr
- .form-group.append-bottom-default
+ .form-group.append-bottom-default.js-secret-runner-token
= f.label :runners_token, "Runner token", class: 'label-light'
- = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ .form-control.js-secret-value-placeholder
+ = '*' * 20
+ = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89'
%p.help-block The secure token used by the Runner to checkout the project
+ %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } }
+ = _('Reveal value')
%hr
.form-group
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index e71d58ec26d..16bcf671c25 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,11 +1,13 @@
+- project = local_assigns.fetch(:project)
+- members = local_assigns.fetch(:members)
+
.panel.panel-default
.panel-heading.flex-project-members-panel
%span.flex-project-title
Members of
- %strong
- #{@project.name}
- %span.badge= @project_members.total_count
- = form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ %strong= project.name
+ %span.badge= members.total_count
+ = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index fd5d3ec56da..d81103c3a92 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -37,5 +37,5 @@
- if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links
- = render 'projects/project_members/team', members: @project_members
+ = render 'projects/project_members/team', project: @project, members: @project_members
= paginate @project_members, theme: "gitlab"
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 031efa903c5..6e105a5521a 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -14,13 +14,13 @@
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.help-block
@@ -28,7 +28,7 @@
.form-group
= label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_area_tag :message, @message, required: false, tabindex: 3, class: 'form-control', rows: 5
+ = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5
.help-block
= s_('TagsPage|Optionally, add a message to the tag.')
%hr
@@ -41,6 +41,6 @@
.help-block
= s_('TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.')
.form-actions
- = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create', tabindex: 3
+ = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create'
= link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel'
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index c51af901699..8c1c532cb3e 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -1,9 +1,12 @@
+- is_lfs_blob = @lfs_blob_ids.include?(blob_item.id)
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span= file_name
+ - if is_lfs_blob
+ %span.label.label-lfs.prepend-left-5 LFS
%td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner'
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 3fcc33044e9..81d07074325 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -2,6 +2,8 @@
- status = label_subscription_status(label, @project).inquiry if current_user
- subject = local_assigns[:subject]
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
+- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
+- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
%li{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
@@ -12,12 +14,14 @@
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right
%ul
- %li
- = link_to_label(label, subject: subject, type: :merge_request) do
- View merge requests
- %li
- = link_to_label(label, subject: subject) do
- View open issues
+ - if show_label_merge_requests_link
+ %li
+ = link_to_label(label, subject: subject, type: :merge_request) do
+ View merge requests
+ - if show_label_issues_link
+ %li
+ = link_to_label(label, subject: subject) do
+ View open issues
- if current_user
%li.label-subscription
- if can_subscribe_to_label_in_different_levels?(label)
@@ -35,13 +39,20 @@
%li
= link_to 'Edit', edit_label_path(label)
%li
- = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
+ = link_to 'Delete',
+ destroy_label_path(label),
+ title: 'Delete',
+ method: :delete,
+ data: {confirm: 'Remove this label? Are you sure?'},
+ class: 'text-danger'
.pull-right.hidden-xs.hidden-sm.hidden-md
- = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
- view merge requests
- = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
- view open issues
+ - if show_label_merge_requests_link
+ = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
+ view merge requests
+ - if show_label_issues_link
+ = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
+ view open issues
- if current_user
.label-subscription.inline
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
index a638b0a805e..8ddb1b2bc99 100644
--- a/app/views/shared/_outdated_browser.html.haml
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -4,5 +4,5 @@
GitLab may not work properly because you are using an outdated web browser.
%br
Please install a
- = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
+ = link_to 'supported web browser', help_page_path('install/requirements', anchor: 'supported-web-browsers')
for a better experience.
diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml
new file mode 100644
index 00000000000..0e816870f15
--- /dev/null
+++ b/app/views/shared/_recaptcha_form.html.haml
@@ -0,0 +1,19 @@
+- resource_name = spammable.class.model_name.singular
+- humanized_resource_name = spammable.class.model_name.human.downcase
+- script = local_assigns.fetch(:script, true)
+- has_submit = local_assigns.fetch(:has_submit, true)
+
+= form_for resource_name, method: :post, html: { class: 'recaptcha-form js-recaptcha-form' } do |f|
+ .recaptcha
+ - params[resource_name].each do |field, value|
+ = hidden_field(resource_name, field, value: value)
+ = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
+ = hidden_field_tag(:recaptcha_verification, true)
+ = recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
+
+ -# Yields a block with given extra params.
+ = yield
+
+ - if has_submit
+ .row-content-block.footer-block
+ = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
diff --git a/app/views/shared/_show_aside.html.haml b/app/views/shared/_show_aside.html.haml
deleted file mode 100644
index 3ac9b11b4fa..00000000000
--- a/app/views/shared/_show_aside.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= link_to '#aside', class: 'show-aside' do
- %i.fa.fa-angle-left
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index de26fa8bbf3..e039a73cd3b 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -6,18 +6,21 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/issues.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- if has_button && current_user
%h4
- The Issue Tracker is the place to add things that need to be improved or solved in a project
+ = _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
%p
- Issues can be bugs, tasks or ideas to be discussed.
- Also, issues are searchable and filterable.
- - if project_select_button
- = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- - else
- = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
+ = _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.")
+ .text-center
+ - if project_select_button
+ = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
+ - else
+ = link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- else
+ %h4.text-center= _("There are no issues to show")
+ %p
+ = _("The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.")
.text-center
- %h4 There are no issues to show.
+ = link_to _('Register / Sign In'), new_user_session_path, class: 'btn btn-success'
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index a65634dce53..04db9de3606 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -2,10 +2,10 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/labels.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- %h4 Labels can be applied to issues and merge requests to categorize them.
- %p You can also star a label to make it a priority label.
+ %h4= _("Labels can be applied to issues and merge requests to categorize them.")
+ %p= _("You can also star a label to make it a priority label.")
- if can?(current_user, :admin_label, @project)
- = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
- = link_to 'Generate a default set of labels', generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
+ = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-new', title: _('New label'), id: 'new_label_link'
+ = link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 67f906903e9..2edf3557df4 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -6,17 +6,18 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/merge_requests.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- if has_button
%h4
- Merge requests are a place to propose changes you've made to a project and discuss those changes with others.
+ = _("Merge requests are a place to propose changes you've made to a project and discuss those changes with others")
%p
- Interested parties can even contribute by pushing commits if they want to.
- - if project_select_button
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: 'New merge request', type: :merge_requests
- - else
- = link_to 'New merge request', button_path, class: 'btn btn-new', title: 'New merge request', id: 'new_merge_request_link'
+ = _("Interested parties can even contribute by pushing commits if they want to.")
+ .text-center
+ - if project_select_button
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('New merge request'), type: :merge_requests
+ - else
+ = link_to _('New merge request'), button_path, class: 'btn btn-new', title: _('New merge request'), id: 'new_merge_request_link'
- else
%h4.text-center
- There are no merge requests to show.
+ = _("There are no merge requests to show")
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 2c27dd638a7..71878e93255 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,9 +1,9 @@
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
+- member = local_assigns.fetch(:member)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
-- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
%li.member{ class: dom_class(member), id: dom_id(member) }
%span.list-item-name
@@ -50,18 +50,17 @@
.controls.member-controls
- if show_controls && member.source == current_resource
- - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
+ - if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default prepend-left-10 hidden-xs',
title: 'Resend invite'
- - if user != current_user && can_admin_member
+ - if user != current_user && member.can_update?
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
- disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text
= member.human_access
@@ -70,23 +69,22 @@
= dropdown_title("Change permissions")
.dropdown-content
%ul
- - member.class.access_level_roles.each do |role, role_id|
+ - member.access_level_roles.each do |role, role_id|
%li
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) }
.prepend-left-5.clearable-input.member-form-control
- = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) }
+ = f.text_field :expires_at,
+ class: 'form-control js-access-expiration-date js-member-update-control',
+ placeholder: 'Expiration date',
+ id: "member_expires_at_#{member.id}",
+ data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input
- else
%span.member-access-text= member.human_access
- - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
- = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
- method: :post,
- class: 'btn btn-default prepend-left-10 visible-xs-block'
-
- - elsif member.request? && can_admin_member
+ - if member.can_approve?
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
class: 'btn btn-success prepend-left-10',
@@ -96,7 +94,7 @@
- unless force_mobile_view
= icon('check inverse', class: 'hidden-xs')
- - if can?(current_user, action_member_permission(:destroy, member), member)
+ - if member.can_remove?
- if current_user == user
= link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
method: :delete,
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 09b9944082f..1fbd6bcc4cb 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,10 +1,13 @@
+- membership_source = local_assigns.fetch(:membership_source)
+- requesters = local_assigns.fetch(:requesters)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
-- if requesters.any?
- .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
- .panel-heading
- Users requesting access to
- %strong= membership_source.name
- %span.badge= requesters.size
- %ul.content-list.members-list
- = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
+- return if requesters.empty?
+
+.panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
+ .panel-heading
+ Users requesting access to
+ %strong= membership_source.name
+ %span.badge= requesters.size
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index c978d9e4821..98e0161f7d1 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -20,8 +20,8 @@
- 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')
+ %span.image-comment-badge
+ = sprite_icon('image-comment-dark')
- elsif note_counter == 0
- counter = badge_counter if local_assigns[:badge_counter]
- badge_class = "hidden" if @fresh_discussion || counter.nil?
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
new file mode 100644
index 00000000000..ba31a5aa9c2
--- /dev/null
+++ b/app/workers/all_queues.yml
@@ -0,0 +1,98 @@
+---
+- cronjob:admin_email
+- cronjob:expire_build_artifacts
+- cronjob:gitlab_usage_ping
+- cronjob:import_export_project_cleanup
+- cronjob:pipeline_schedule
+- cronjob:prune_old_events
+- cronjob:remove_expired_group_links
+- cronjob:remove_expired_members
+- cronjob:remove_old_web_hook_logs
+- cronjob:remove_unreferenced_lfs_objects
+- cronjob:repository_archive_cache
+- cronjob:repository_check_batch
+- cronjob:requests_profiles
+- cronjob:schedule_update_user_activity
+- cronjob:stuck_ci_jobs
+- cronjob:stuck_import_jobs
+- cronjob:stuck_merge_jobs
+- cronjob:trending_projects
+
+- gcp_cluster:cluster_install_app
+- gcp_cluster:cluster_provision
+- gcp_cluster:cluster_wait_for_app_installation
+- gcp_cluster:wait_for_cluster_creation
+
+- github_import_advance_stage
+- github_importer:github_import_import_diff_note
+- github_importer:github_import_import_issue
+- github_importer:github_import_import_note
+- github_importer:github_import_import_pull_request
+- github_importer:github_import_refresh_import_jid
+- github_importer:github_import_stage_finish_import
+- github_importer:github_import_stage_import_base_data
+- github_importer:github_import_stage_import_issues_and_diff_notes
+- github_importer:github_import_stage_import_notes
+- github_importer:github_import_stage_import_pull_requests
+- github_importer:github_import_stage_import_repository
+
+- pipeline_cache:expire_job_cache
+- pipeline_cache:expire_pipeline_cache
+- pipeline_creation:create_pipeline
+- pipeline_default:build_coverage
+- pipeline_default:build_trace_sections
+- pipeline_default:pipeline_metrics
+- pipeline_default:pipeline_notification
+- pipeline_default:update_head_pipeline_for_merge_request
+- pipeline_hooks:build_hooks
+- pipeline_hooks:pipeline_hooks
+- pipeline_processing:build_finished
+- pipeline_processing:build_queue
+- pipeline_processing:build_success
+- pipeline_processing:pipeline_process
+- pipeline_processing:pipeline_success
+- pipeline_processing:pipeline_update
+- pipeline_processing:stage_update
+
+- repository_check:repository_check_clear
+- repository_check:repository_check_single_repository
+
+- default
+- mailers # ActionMailer::DeliveryJob.queue_name
+
+- authorized_projects
+- background_migration
+- create_gpg_signature
+- delete_merged_branches
+- delete_user
+- email_receiver
+- emails_on_push
+- expire_build_instance_artifacts
+- git_garbage_collect
+- gitlab_shell
+- group_destroy
+- invalid_gpg_signature_update
+- irker
+- merge
+- namespaceless_project_destroy
+- new_issue
+- new_merge_request
+- new_note
+- pages
+- post_receive
+- process_commit
+- project_cache
+- project_destroy
+- project_export
+- project_migrate_hashed_storage
+- project_service
+- propagate_service_template
+- reactive_caching
+- repository_fork
+- repository_import
+- storage_migrator
+- system_hook_push
+- update_merge_requests
+- update_user_activity
+- upload_checksum
+- web_hook
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 5efa9180f5e..97d80305bec 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -2,7 +2,7 @@ class BuildFinishedWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
index 6705a1c2709..cbfca8c342c 100644
--- a/app/workers/build_hooks_worker.rb
+++ b/app/workers/build_hooks_worker.rb
@@ -2,7 +2,7 @@ class BuildHooksWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :hooks
+ queue_namespace :pipeline_hooks
def perform(build_id)
Ci::Build.find_by(id: build_id)
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
index fc775a84dc0..e4f4e6c1d9e 100644
--- a/app/workers/build_queue_worker.rb
+++ b/app/workers/build_queue_worker.rb
@@ -2,7 +2,7 @@ class BuildQueueWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
index ec049821ad7..4b9097bc5e4 100644
--- a/app/workers/build_success_worker.rb
+++ b/app/workers/build_success_worker.rb
@@ -2,7 +2,7 @@ class BuildSuccessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index 9c3bdabc49e..37586e161c9 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -3,13 +3,23 @@ Sidekiq::Worker.extend ActiveSupport::Concern
module ApplicationWorker
extend ActiveSupport::Concern
- include Sidekiq::Worker
+ include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
included do
- sidekiq_options queue: base_queue_name
+ set_queue
end
module ClassMethods
+ def inherited(subclass)
+ subclass.set_queue
+ end
+
+ def set_queue
+ queue_name = [queue_namespace, base_queue_name].compact.join(':')
+
+ sidekiq_options queue: queue_name # rubocop:disable Cop/SidekiqOptionsQueue
+ end
+
def base_queue_name
name
.sub(/\AGitlab::/, '')
@@ -18,6 +28,16 @@ module ApplicationWorker
.tr('/', '_')
end
+ def queue_namespace(new_namespace = nil)
+ if new_namespace
+ sidekiq_options queue_namespace: new_namespace
+
+ set_queue
+ else
+ get_sidekiq_options['queue_namespace']&.to_s
+ end
+ end
+
def queue
get_sidekiq_options['queue'].to_s
end
diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb
index a5074d13220..24b9f145220 100644
--- a/app/workers/concerns/cluster_queue.rb
+++ b/app/workers/concerns/cluster_queue.rb
@@ -5,6 +5,6 @@ module ClusterQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :gcp_cluster
+ queue_namespace :gcp_cluster
end
end
diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb
index e918bb011e0..b6581779f6a 100644
--- a/app/workers/concerns/cronjob_queue.rb
+++ b/app/workers/concerns/cronjob_queue.rb
@@ -4,6 +4,7 @@ module CronjobQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :cronjob, retry: false
+ queue_namespace :cronjob
+ sidekiq_options retry: false
end
end
diff --git a/app/workers/concerns/gitlab/github_import/queue.rb b/app/workers/concerns/gitlab/github_import/queue.rb
index a2bee361b86..22c2ce458e8 100644
--- a/app/workers/concerns/gitlab/github_import/queue.rb
+++ b/app/workers/concerns/gitlab/github_import/queue.rb
@@ -4,12 +4,14 @@ module Gitlab
extend ActiveSupport::Concern
included do
+ queue_namespace :github_importer
+
# If a job produces an error it may block a stage from advancing
# forever. To prevent this from happening we prevent jobs from going to
# the dead queue. This does mean some resources may not be imported, but
# this is better than a project being stuck in the "import" state
# forever.
- sidekiq_options queue: 'github_importer', dead: false, retry: 5
+ sidekiq_options dead: false, retry: 5
end
end
end
diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb
index eb0d6c9c36c..526ed0bad07 100644
--- a/app/workers/concerns/new_issuable.rb
+++ b/app/workers/concerns/new_issuable.rb
@@ -9,15 +9,15 @@ module NewIssuable
end
def set_user(user_id)
- @user = User.find_by(id: user_id)
+ @user = User.find_by(id: user_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- log_error(User, user_id) unless @user
+ log_error(User, user_id) unless @user # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def set_issuable(issuable_id)
- @issuable = issuable_class.find_by(id: issuable_id)
+ @issuable = issuable_class.find_by(id: issuable_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- log_error(issuable_class, issuable_id) unless @issuable
+ log_error(issuable_class, issuable_id) unless @issuable # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def log_error(record_class, record_id)
diff --git a/app/workers/concerns/pipeline_queue.rb b/app/workers/concerns/pipeline_queue.rb
index ddf45b91345..e77093a6902 100644
--- a/app/workers/concerns/pipeline_queue.rb
+++ b/app/workers/concerns/pipeline_queue.rb
@@ -5,14 +5,6 @@ module PipelineQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: 'pipeline_default'
- end
-
- class_methods do
- def enqueue_in(group:)
- raise ArgumentError, 'Unspecified queue group!' if group.empty?
-
- sidekiq_options queue: "pipeline_#{group}"
- end
+ queue_namespace :pipeline_default
end
end
diff --git a/app/workers/concerns/repository_check_queue.rb b/app/workers/concerns/repository_check_queue.rb
index a597321ccf4..43fb66c31b0 100644
--- a/app/workers/concerns/repository_check_queue.rb
+++ b/app/workers/concerns/repository_check_queue.rb
@@ -3,6 +3,8 @@ module RepositoryCheckQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :repository_check, retry: false
+ queue_namespace :repository_check
+
+ sidekiq_options retry: false
end
end
diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb
index 00cd7b85b9f..c3ac35e54f5 100644
--- a/app/workers/create_pipeline_worker.rb
+++ b/app/workers/create_pipeline_worker.rb
@@ -2,7 +2,7 @@ class CreatePipelineWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :creation
+ queue_namespace :pipeline_creation
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index a591e2da519..7217364a9f2 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -2,7 +2,7 @@ class ExpireJobCacheWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :cache
+ queue_namespace :pipeline_cache
def perform(job_id)
job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id)
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index a3ac32b437d..3e34de22c19 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -2,7 +2,7 @@ class ExpirePipelineCacheWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :cache
+ queue_namespace :pipeline_cache
def perform(pipeline_id)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb
index 400396d5755..f7f498af840 100644
--- a/app/workers/gitlab/github_import/advance_stage_worker.rb
+++ b/app/workers/gitlab/github_import/advance_stage_worker.rb
@@ -9,7 +9,7 @@ module Gitlab
class AdvanceStageWorker
include ApplicationWorker
- sidekiq_options queue: 'github_importer_advance_stage', dead: false
+ sidekiq_options dead: false
INTERVAL = 30.seconds.to_i
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index 62f733c02fc..3ec81d040b4 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -1,7 +1,7 @@
class PagesWorker
include ApplicationWorker
- sidekiq_options queue: :pages, retry: false
+ sidekiq_options retry: false
def perform(action, *arg)
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb
index 661c29efe88..c94918ff4ee 100644
--- a/app/workers/pipeline_hooks_worker.rb
+++ b/app/workers/pipeline_hooks_worker.rb
@@ -2,7 +2,7 @@ class PipelineHooksWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :hooks
+ queue_namespace :pipeline_hooks
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 07dbf6a971e..24424b3f472 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -2,7 +2,7 @@ class PipelineProcessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 68c40a259e1..2ab0739a17f 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -2,7 +2,7 @@ class PipelineSuccessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb
index 24a8a9fbed5..fc9da2d45b1 100644
--- a/app/workers/pipeline_update_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -2,7 +2,7 @@ class PipelineUpdateWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/stage_update_worker.rb b/app/workers/stage_update_worker.rb
index 69f2318d83b..e4b683fca33 100644
--- a/app/workers/stage_update_worker.rb
+++ b/app/workers/stage_update_worker.rb
@@ -2,7 +2,7 @@ class StageUpdateWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(stage_id)
Ci::Stage.find_by(id: stage_id).try do |stage|
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index 36d2a2e6466..16394293c79 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -23,7 +23,12 @@ class StuckMergeJobsWorker
merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
- merge_requests.where(merge_commit_sha: nil).update_all(state: :opened, merge_jid: nil)
+
+ merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil)
+
+ # Do not reopen merge requests using direct queries.
+ # We rely on state machine callbacks to update head_pipeline_id
+ merge_requests_to_reopen.each(&:unlock_mr)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 0a2e9b63578..f09d89aa170 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -1,15 +1,25 @@
class UpdateHeadPipelineForMergeRequestWorker
include ApplicationWorker
-
- sidekiq_options queue: 'pipeline_default'
+ include PipelineQueue
def perform(merge_request_id)
merge_request = MergeRequest.find(merge_request_id)
pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
return unless pipeline && pipeline.latest?
- raise ArgumentError, 'merge request sha does not equal pipeline sha' if merge_request.diff_head_sha != pipeline.sha
+
+ if merge_request.diff_head_sha != pipeline.sha
+ log_error_message_for(merge_request)
+
+ return
+ end
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
+
+ def log_error_message_for(merge_request)
+ Rails.logger.error(
+ "Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}"
+ )
+ end
end