summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/activities.js12
-rw-r--r--app/assets/javascripts/api.js7
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es63
-rw-r--r--app/assets/javascripts/boards/components/board.js.es68
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js.es66
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es69
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js.es658
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js.es62
-rw-r--r--app/assets/javascripts/boards/models/list.js.es611
-rw-r--r--app/assets/javascripts/boards/services/board_service.js.es616
-rw-r--r--app/assets/javascripts/build.js36
-rw-r--r--app/assets/javascripts/commit/image_file.js (renamed from app/assets/javascripts/commit/image-file.js)0
-rw-r--r--app/assets/javascripts/compare_autocomplete.js.es6 (renamed from app/assets/javascripts/compare_autocomplete.js)13
-rw-r--r--app/assets/javascripts/cycle_analytics.js.es6 (renamed from app/assets/javascripts/cycle-analytics.js.es6)0
-rw-r--r--app/assets/javascripts/dispatcher.js.es6 (renamed from app/assets/javascripts/dispatcher.js)18
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es638
-rw-r--r--app/assets/javascripts/gl_dropdown.js89
-rw-r--r--app/assets/javascripts/gl_field_errors.js.es6167
-rw-r--r--app/assets/javascripts/groups.js13
-rw-r--r--app/assets/javascripts/issues_bulk_assignment.js.es6 (renamed from app/assets/javascripts/issues-bulk-assignment.js.es6)0
-rw-r--r--app/assets/javascripts/label_manager.js.es6 (renamed from app/assets/javascripts/LabelManager.js.es6)0
-rw-r--r--app/assets/javascripts/labels_select.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js5
-rw-r--r--app/assets/javascripts/member_expiration_date.js8
-rw-r--r--app/assets/javascripts/members.js.es636
-rw-r--r--app/assets/javascripts/merge_request_tabs.js64
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es6 (renamed from app/assets/javascripts/merge_request_widget.js)76
-rw-r--r--app/assets/javascripts/milestone_select.js2
-rw-r--r--app/assets/javascripts/network/branch_graph.js (renamed from app/assets/javascripts/network/branch-graph.js)0
-rw-r--r--app/assets/javascripts/pipeline.js.es615
-rw-r--r--app/assets/javascripts/pipelines.js.es640
-rw-r--r--app/assets/javascripts/project_members.js10
-rw-r--r--app/assets/javascripts/project_new.js4
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js.es68
-rw-r--r--app/assets/javascripts/user_tabs.js.es611
-rw-r--r--app/assets/javascripts/username_validator.js.es6133
-rw-r--r--app/assets/javascripts/users_select.js15
-rw-r--r--app/assets/stylesheets/behaviors.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss4
-rw-r--r--app/assets/stylesheets/framework/blocks.scss4
-rw-r--r--app/assets/stylesheets/framework/buttons.scss26
-rw-r--r--app/assets/stylesheets/framework/callout.scss5
-rw-r--r--app/assets/stylesheets/framework/common.scss39
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss19
-rw-r--r--app/assets/stylesheets/framework/files.scss18
-rw-r--r--app/assets/stylesheets/framework/forms.scss15
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss5
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss13
-rw-r--r--app/assets/stylesheets/framework/logo.scss85
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss19
-rw-r--r--app/assets/stylesheets/framework/mobile.scss2
-rw-r--r--app/assets/stylesheets/framework/nav.scss1
-rw-r--r--app/assets/stylesheets/framework/panels.scss5
-rw-r--r--app/assets/stylesheets/framework/selects.scss31
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss6
-rw-r--r--app/assets/stylesheets/framework/timeline.scss1
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss10
-rw-r--r--app/assets/stylesheets/framework/typography.scss3
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/highlight/dark.scss128
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss118
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss136
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss136
-rw-r--r--app/assets/stylesheets/highlight/white.scss2
-rw-r--r--app/assets/stylesheets/mailers/repository_push_email.scss2
-rw-r--r--app/assets/stylesheets/notify.scss10
-rw-r--r--app/assets/stylesheets/pages/admin.scss12
-rw-r--r--app/assets/stylesheets/pages/boards.scss32
-rw-r--r--app/assets/stylesheets/pages/builds.scss8
-rw-r--r--app/assets/stylesheets/pages/commit.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss1
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/dashboard.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss37
-rw-r--r--app/assets/stylesheets/pages/editor.scss7
-rw-r--r--app/assets/stylesheets/pages/environments.scss35
-rw-r--r--app/assets/stylesheets/pages/events.scss10
-rw-r--r--app/assets/stylesheets/pages/groups.scss15
-rw-r--r--app/assets/stylesheets/pages/help.scss8
-rw-r--r--app/assets/stylesheets/pages/issuable.scss8
-rw-r--r--app/assets/stylesheets/pages/issues.scss1
-rw-r--r--app/assets/stylesheets/pages/labels.scss12
-rw-r--r--app/assets/stylesheets/pages/lint.scss1
-rw-r--r--app/assets/stylesheets/pages/login.scss176
-rw-r--r--app/assets/stylesheets/pages/members.scss98
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss49
-rw-r--r--app/assets/stylesheets/pages/milestone.scss1
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss12
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss204
-rw-r--r--app/assets/stylesheets/pages/profile.scss26
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss26
-rw-r--r--app/assets/stylesheets/pages/runners.scss1
-rw-r--r--app/assets/stylesheets/pages/status.scss4
-rw-r--r--app/assets/stylesheets/pages/tree.scss1
-rw-r--r--app/assets/stylesheets/pages/xterm.scss290
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb2
-rw-r--r--app/controllers/application_controller.rb7
-rw-r--r--app/controllers/ci/application_controller.rb7
-rw-r--r--app/controllers/ci/lints_controller.rb2
-rw-r--r--app/controllers/ci/projects_controller.rb2
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/namespaces_controller.rb25
-rw-r--r--app/controllers/projects/board_lists_controller.rb65
-rw-r--r--app/controllers/projects/boards/issues_controller.rb40
-rw-r--r--app/controllers/projects/boards/lists_controller.rb18
-rw-r--r--app/controllers/projects/boards_controller.rb28
-rw-r--r--app/controllers/projects/graphs_controller.rb6
-rw-r--r--app/controllers/projects/group_links_controller.rb20
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb113
-rw-r--r--app/controllers/projects/project_members_controller.rb36
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb14
-rw-r--r--app/controllers/users_controller.rb6
-rw-r--r--app/finders/trending_projects_finder.rb16
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_settings_helper.rb12
-rw-r--r--app/helpers/avatars_helper.rb5
-rw-r--r--app/helpers/boards_helper.rb12
-rw-r--r--app/helpers/broadcast_messages_helper.rb6
-rw-r--r--app/helpers/button_helper.rb5
-rw-r--r--app/helpers/gitlab_markdown_helper.rb44
-rw-r--r--app/helpers/issues_helper.rb5
-rw-r--r--app/helpers/merge_requests_helper.rb17
-rw-r--r--app/helpers/search_helper.rb14
-rw-r--r--app/models/abuse_report.rb7
-rw-r--r--app/models/appearance.rb4
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/broadcast_message.rb3
-rw-r--r--app/models/ci/build.rb22
-rw-r--r--app/models/ci/pipeline.rb23
-rw-r--r--app/models/commit_status.rb28
-rw-r--r--app/models/compare.rb21
-rw-r--r--app/models/concerns/cache_markdown_field.rb131
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/cycle_analytics.rb14
-rw-r--r--app/models/deployment.rb13
-rw-r--r--app/models/environment.rb15
-rw-r--r--app/models/event.rb10
-rw-r--r--app/models/global_label.rb4
-rw-r--r--app/models/global_milestone.rb5
-rw-r--r--app/models/label.rb3
-rw-r--r--app/models/member.rb7
-rw-r--r--app/models/merge_request.rb17
-rw-r--r--app/models/merge_request_diff.rb21
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/namespace.rb17
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/project.rb36
-rw-r--r--app/models/project_group_link.rb2
-rw-r--r--app/models/release.rb4
-rw-r--r--app/models/repository.rb63
-rw-r--r--app/models/snippet.rb10
-rw-r--r--app/models/trending_project.rb35
-rw-r--r--app/models/user.rb7
-rw-r--r--app/policies/project_policy.rb21
-rw-r--r--app/services/base_service.rb7
-rw-r--r--app/services/boards/base_service.rb5
-rw-r--r--app/services/boards/create_service.rb17
-rw-r--r--app/services/boards/issues/create_service.rb23
-rw-r--r--app/services/boards/issues/list_service.rb13
-rw-r--r--app/services/boards/issues/move_service.rb8
-rw-r--r--app/services/boards/list_service.rb14
-rw-r--r--app/services/boards/lists/create_service.rb12
-rw-r--r--app/services/boards/lists/destroy_service.rb6
-rw-r--r--app/services/boards/lists/generate_service.rb16
-rw-r--r--app/services/boards/lists/list_service.rb9
-rw-r--r--app/services/boards/lists/move_service.rb5
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/services/compare_service.rb7
-rw-r--r--app/services/create_deployment_service.rb41
-rw-r--r--app/services/files/base_service.rb11
-rw-r--r--app/services/files/multi_service.rb124
-rw-r--r--app/services/files/update_service.rb6
-rw-r--r--app/services/git_push_service.rb15
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb4
-rw-r--r--app/services/merge_requests/assign_issues_service.rb35
-rw-r--r--app/services/merge_requests/base_service.rb27
-rw-r--r--app/services/merge_requests/build_service.rb23
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb9
-rw-r--r--app/services/merge_requests/update_service.rb5
-rw-r--r--app/services/notification_service.rb6
-rw-r--r--app/services/slash_commands/interpret_service.rb21
-rw-r--r--app/services/todo_service.rb6
-rw-r--r--app/validators/namespace_validator.rb1
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml2
-rw-r--r--app/views/admin/appearances/preview.html.haml17
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml5
-rw-r--r--app/views/admin/broadcast_messages/preview.js.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml2
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/devise/confirmations/almost_there.haml4
-rw-r--r--app/views/devise/confirmations/new.html.haml12
-rw-r--r--app/views/devise/passwords/edit.html.haml20
-rw-r--r--app/views/devise/passwords/new.html.haml10
-rw-r--r--app/views/devise/sessions/_new_base.html.haml12
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml10
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml10
-rw-r--r--app/views/devise/sessions/new.html.haml23
-rw-r--r--app/views/devise/sessions/two_factor.html.haml17
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml15
-rw-r--r--app/views/devise/shared/_sign_in_link.html.haml1
-rw-r--r--app/views/devise/shared/_signin_box.html.haml37
-rw-r--r--app/views/devise/shared/_signup_box.html.haml37
-rw-r--r--app/views/devise/shared/_tab_single.html.haml3
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml10
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml5
-rw-r--r--app/views/devise/unlocks/new.html.haml10
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml4
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml35
-rw-r--r--app/views/groups/group_members/index.html.haml40
-rw-r--r--app/views/groups/group_members/update.js.haml4
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml9
-rw-r--r--app/views/layouts/application.html.haml1
-rw-r--r--app/views/layouts/devise.html.haml63
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_zen.html.haml4
-rw-r--r--app/views/projects/blame/show.html.haml87
-rw-r--r--app/views/projects/blob/edit.html.haml43
-rw-r--r--app/views/projects/boards/components/_board.html.haml41
-rw-r--r--app/views/projects/boards/components/_card.html.haml6
-rw-r--r--app/views/projects/boards/index.html.haml16
-rw-r--r--app/views/projects/boards/show.html.haml5
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/builds/_table.html.haml2
-rw-r--r--app/views/projects/builds/show.html.haml95
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build_pipeline.html.haml6
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml8
-rw-r--r--app/views/projects/commit/_pipeline.html.haml81
-rw-r--r--app/views/projects/commit/_pipeline_stage.html.haml2
-rw-r--r--app/views/projects/commit/_pipeline_status_group.html.haml20
-rw-r--r--app/views/projects/commit/_pipelines_list.haml2
-rw-r--r--app/views/projects/commit/builds.html.haml11
-rw-r--r--app/views/projects/commit/show.html.haml25
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/compare/_ref_dropdown.html.haml4
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml8
-rw-r--r--app/views/projects/deployments/_deployment.html.haml8
-rw-r--r--app/views/projects/diffs/_diffs.html.haml3
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/edit.html.haml5
-rw-r--r--app/views/projects/environments/_environment.html.haml13
-rw-r--r--app/views/projects/environments/edit.html.haml11
-rw-r--r--app/views/projects/environments/index.html.haml46
-rw-r--r--app/views/projects/environments/new.html.haml11
-rw-r--r--app/views/projects/environments/show.html.haml43
-rw-r--r--app/views/projects/forks/index.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml6
-rw-r--r--app/views/projects/group_links/index.html.haml2
-rw-r--r--app/views/projects/group_links/update.js.haml3
-rw-r--r--app/views/projects/issues/_head.html.haml2
-rw-r--r--app/views/projects/issues/edit.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--app/views/projects/labels/_label.html.haml2
-rw-r--r--app/views/projects/labels/edit.html.haml11
-rw-r--r--app/views/projects/labels/new.html.haml11
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml13
-rw-r--r--app/views/projects/merge_requests/_new_diffs.html.haml1
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml62
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml14
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml19
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml1
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml3
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/milestones/edit.html.haml12
-rw-r--r--app/views/projects/milestones/new.html.haml11
-rw-r--r--app/views/projects/milestones/show.html.haml83
-rw-r--r--app/views/projects/network/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/pipelines/_head.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--app/views/projects/pipelines/index.html.haml2
-rw-r--r--app/views/projects/pipelines/show.html.haml13
-rw-r--r--app/views/projects/project_members/_group_members.html.haml2
-rw-r--r--app/views/projects/project_members/_groups.html.haml7
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml35
-rw-r--r--app/views/projects/project_members/_team.html.haml16
-rw-r--r--app/views/projects/project_members/index.html.haml40
-rw-r--r--app/views/projects/project_members/update.js.haml4
-rw-r--r--app/views/projects/repositories/_feed.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml4
-rw-r--r--app/views/projects/snippets/_actions.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/search/results/_note.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml1
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_new_project_item_select.html.haml2
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/groups/_group.html.haml2
-rw-r--r--app/views/shared/icons/_illustration_no_commits.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml8
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml3
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml3
-rw-r--r--app/views/shared/members/_group.html.haml29
-rw-r--r--app/views/shared/members/_member.html.haml108
-rw-r--r--app/views/shared/members/_requests.html.haml2
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml3
-rw-r--r--app/views/shared/notifications/_button.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/snippets/_blob.html.haml7
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml2
-rw-r--r--app/views/snippets/show.html.haml1
-rw-r--r--app/views/u2f/_authenticate.html.haml2
-rw-r--r--app/views/users/show.html.haml6
-rw-r--r--app/workers/build_coverage_worker.rb9
-rw-r--r--app/workers/build_finished_worker.rb10
-rw-r--r--app/workers/build_hooks_worker.rb9
-rw-r--r--app/workers/build_success_worker.rb27
-rw-r--r--app/workers/clear_database_cache_worker.rb23
-rw-r--r--app/workers/expire_build_artifacts_worker.rb11
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb11
-rw-r--r--app/workers/pipeline_hooks_worker.rb9
-rw-r--r--app/workers/pipeline_process_worker.rb (renamed from app/workers/process_pipeline_worker.rb)2
-rw-r--r--app/workers/pipeline_success_worker.rb12
-rw-r--r--app/workers/pipeline_update_worker.rb (renamed from app/workers/update_pipeline_worker.rb)2
-rw-r--r--app/workers/trending_projects_worker.rb11
-rw-r--r--app/workers/update_merge_requests_worker.rb16
351 files changed, 4360 insertions, 1964 deletions
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index d5e11e22be5..f4f8cf04184 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -21,16 +21,14 @@
};
Activities.prototype.toggleFilter = function(sender) {
- var event_filters, filter;
+ var filter = sender.attr("id").split("_")[0];
+
$('.event-filter .active').removeClass("active");
- event_filters = $.cookie("event_filter");
- filter = sender.attr("id").split("_")[0];
- $.cookie("event_filter", (event_filters !== filter ? filter : ""), {
+ $.cookie("event_filter", filter, {
path: gon.relative_url_root || '/'
});
- if (event_filters !== filter) {
- return sender.closest('li').toggleClass("active");
- }
+
+ sender.closest('li').toggleClass("active");
};
return Activities;
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 599331df3f5..56ec1489f89 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -6,11 +6,10 @@
groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/:namespace_path/:project_path/labels",
- licensePath: "/api/:version/licenses/:key",
- gitignorePath: "/api/:version/gitignores/:key",
- gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
+ licensePath: "/api/:version/templates/licenses/:key",
+ gitignorePath: "/api/:version/templates/gitignores/:key",
+ gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
-
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id);
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index 91c12570e09..d4f8f4b9420 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -28,12 +28,13 @@ $(() => {
state: Store.state,
loading: true,
endpoint: $boardApp.dataset.endpoint,
+ boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase
},
init: Store.create.bind(Store),
created () {
- gl.boardService = new BoardService(this.endpoint);
+ gl.boardService = new BoardService(this.endpoint, this.boardId);
},
ready () {
Store.disabled = this.disabled;
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6
index 7e86f001f44..cacb36a897f 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js.es6
@@ -21,7 +21,8 @@
},
data () {
return {
- filters: Store.state.filters
+ filters: Store.state.filters,
+ showIssueForm: false
};
},
watch: {
@@ -33,6 +34,11 @@
deep: true
}
},
+ methods: {
+ showNewIssueForm() {
+ this.showIssueForm = !this.showIssueForm;
+ }
+ },
ready () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled,
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js.es6
index 63d72d857d9..ff90f2d6d75 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js.es6
+++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6
@@ -8,10 +8,8 @@
data () {
return {
predefinedLabels: [
- new ListLabel({ title: 'Development', color: '#5CB85C' }),
- new ListLabel({ title: 'Testing', color: '#F0AD4E' }),
- new ListLabel({ title: 'Production', color: '#FF5F00' }),
- new ListLabel({ title: 'Ready', color: '#FF0000' })
+ new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
+ new ListLabel({ title: 'Doing', color: '#5CB85C' })
]
}
},
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 474805c1437..7022a29e818 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -1,4 +1,5 @@
//= require ./board_card
+//= require ./board_new_issue
(() => {
const Store = gl.issueBoards.BoardsStore;
@@ -8,14 +9,16 @@
gl.issueBoards.BoardList = Vue.extend({
components: {
- 'board-card': gl.issueBoards.BoardCard
+ 'board-card': gl.issueBoards.BoardCard,
+ 'board-new-issue': gl.issueBoards.BoardNewIssue
},
props: {
disabled: Boolean,
list: Object,
issues: Array,
loading: Boolean,
- issueLinkBase: String
+ issueLinkBase: String,
+ showIssueForm: Boolean
},
data () {
return {
@@ -73,7 +76,7 @@
group: 'issues',
sort: false,
disabled: this.disabled,
- filter: '.board-list-count',
+ filter: '.board-list-count, .is-disabled',
onStart: (e) => {
const card = this.$refs.issue[e.oldIndex];
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6
new file mode 100644
index 00000000000..a4fad422eca
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6
@@ -0,0 +1,58 @@
+(() => {
+ window.gl = window.gl || {};
+
+ gl.issueBoards.BoardNewIssue = Vue.extend({
+ props: {
+ list: Object,
+ showIssueForm: Boolean
+ },
+ data() {
+ return {
+ title: '',
+ error: false
+ };
+ },
+ watch: {
+ showIssueForm () {
+ this.$els.input.focus();
+ }
+ },
+ methods: {
+ submit(e) {
+ e.preventDefault();
+ if (this.title.trim() === '') return;
+
+ this.error = false;
+
+ const labels = this.list.label ? [this.list.label] : [];
+ const issue = new ListIssue({
+ title: this.title,
+ labels
+ });
+
+ this.list.newIssue(issue)
+ .then((data) => {
+ // Need this because our jQuery very kindly disables buttons on ALL form submissions
+ $(this.$els.submitButton).enable();
+ })
+ .catch(() => {
+ // Need this because our jQuery very kindly disables buttons on ALL form submissions
+ $(this.$els.submitButton).enable();
+
+ // Remove the issue
+ this.list.removeIssue(issue);
+
+ // Show error message
+ this.error = true;
+ this.showIssueForm = true;
+ });
+
+ this.cancel();
+ },
+ cancel() {
+ this.showIssueForm = false;
+ this.title = '';
+ }
+ }
+ });
+})();
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
index 44addb3ea98..f629d45c587 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
@@ -21,7 +21,7 @@
fallbackClass: 'is-dragging',
fallbackOnBody: true,
ghostClass: 'is-ghost',
- filter: '.has-tooltip',
+ filter: '.has-tooltip, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index 91fd620fdb3..5d0a561cdba 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -87,6 +87,17 @@ class List {
});
}
+ newIssue (issue) {
+ this.addIssue(issue);
+ this.issuesSize++;
+
+ return gl.boardService.newIssue(this.id, issue)
+ .then((resp) => {
+ const data = resp.json();
+ issue.id = data.iid;
+ });
+ }
+
createIssues (data) {
data.forEach((issueObj) => {
this.addIssue(new ListIssue(issueObj));
diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6
index 9b80fb2e99f..b9c91cbf31e 100644
--- a/app/assets/javascripts/boards/services/board_service.js.es6
+++ b/app/assets/javascripts/boards/services/board_service.js.es6
@@ -1,15 +1,15 @@
class BoardService {
- constructor (root) {
+ constructor (root, boardId) {
Vue.http.options.root = root;
- this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
+ this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: {
method: 'POST',
- url: `${root}/lists/generate.json`
+ url: `${root}/${boardId}/lists/generate.json`
}
});
- this.issue = Vue.resource(`${root}/issues{/id}`, {});
- this.issues = Vue.resource(`${root}/lists{/id}/issues`, {});
+ this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
+ this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
@@ -58,4 +58,10 @@ class BoardService {
to_list_id
});
}
+
+ newIssue (id, issue) {
+ return this.issues.save({ id }, {
+ issue
+ });
+ }
};
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index f336bfc36d6..97462a5959c 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -15,18 +15,17 @@
this.hideSidebar = bind(this.hideSidebar, this);
this.toggleSidebar = bind(this.toggleSidebar, this);
this.updateDropdown = bind(this.updateDropdown, this);
+ this.$document = $(document);
clearInterval(Build.interval);
// Init breakpoint checker
this.bp = Breakpoints.get();
- $('.js-build-sidebar').niceScroll();
+ this.initSidebar();
this.populateJobs(this.build_stage);
this.updateStageDropdownText(this.build_stage);
- this.hideSidebar();
- $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
$(window).off('resize.build').on('resize.build', this.hideSidebar);
- $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
+ this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
$('#js-build-scroll > a').off('click').on('click', this.stepTrace);
this.updateArtifactRemoveDate();
if ($('#build-trace').length) {
@@ -62,6 +61,21 @@
}
}
+ Build.prototype.initSidebar = function() {
+ this.$sidebar = $('.js-build-sidebar');
+ this.sidebarTranslationLimits = {
+ min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
+ }
+ this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
+ this.$sidebar.css({
+ top: this.sidebarTranslationLimits.max
+ });
+ this.$sidebar.niceScroll();
+ this.hideSidebar();
+ this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
+ this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
+ };
+
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
@@ -129,15 +143,23 @@
Build.prototype.toggleSidebar = function() {
if (this.shouldHideSidebar()) {
- return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed');
+ return this.$sidebar.toggleClass('right-sidebar-expanded right-sidebar-collapsed');
}
};
+ Build.prototype.translateSidebar = function(e) {
+ var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop;
+ if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
+ this.$sidebar.css({
+ top: newPosition
+ });
+ };
+
Build.prototype.hideSidebar = function() {
if (this.shouldHideSidebar()) {
- return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ return this.$sidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
} else {
- return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ return this.$sidebar.removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
}
};
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image_file.js
index e893491b19b..e893491b19b 100644
--- a/app/assets/javascripts/commit/image-file.js
+++ b/app/assets/javascripts/commit/image_file.js
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js.es6
index 294d2c9052c..9a2082d97e0 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js.es6
@@ -9,7 +9,10 @@
var $dropdown, selected;
$dropdown = $(this);
selected = $dropdown.data('selected');
- return $dropdown.glDropdown({
+ 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'),
@@ -42,6 +45,14 @@
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');
+ });
});
};
diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6
index cd9886ba58d..cd9886ba58d 100644
--- a/app/assets/javascripts/cycle-analytics.js.es6
+++ b/app/assets/javascripts/cycle_analytics.js.es6
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js.es6
index 8d99b12102d..f3957ed374b 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -8,6 +8,7 @@
Dispatcher = (function() {
function Dispatcher() {
this.initSearch();
+ this.initFieldErrors();
this.initPageScripts();
}
@@ -20,7 +21,11 @@
path = page.split(':');
shortcut_handler = null;
switch (page) {
+ case 'sessions:new':
+ new UsernameValidator();
+ break;
case 'projects:boards:show':
+ case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:merge_requests:index':
@@ -126,6 +131,9 @@
new TreeView();
}
break;
+ case 'projects:pipelines:show':
+ new gl.Pipelines();
+ break;
case 'groups:activity':
new Activities();
break;
@@ -136,12 +144,12 @@
break;
case 'groups:group_members:index':
new gl.MemberExpirationDate();
- new GroupMembers();
+ new gl.Members();
new UsersSelect();
break;
case 'projects:project_members:index':
new gl.MemberExpirationDate();
- new ProjectMembers();
+ new gl.Members();
new UsersSelect();
break;
case 'groups:new':
@@ -287,6 +295,12 @@
}
};
+ Dispatcher.prototype.initFieldErrors = function() {
+ $('.show-gl-field-errors').each((i, form) => {
+ new gl.GlFieldErrors(form);
+ });
+ };
+
return Dispatcher;
})();
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index d0786bf0053..845313b6b38 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -52,37 +52,27 @@
}
}
},
- setup: function(input) {
+ setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input');
// destroy previous instances
this.destroyAtWho();
// set up instances
this.setupAtWho();
- if (this.dataSource) {
- if (!this.dataLoading && !this.cachedData) {
- this.dataLoading = true;
- setTimeout((function(_this) {
- return function() {
- var fetch;
- fetch = _this.fetchData(_this.dataSource);
- return fetch.done(function(data) {
- _this.dataLoading = false;
- return _this.loadData(data);
- });
- };
- // We should wait until initializations are done
- // and only trigger the last .setup since
- // The previous .dataSource belongs to the previous issuable
- // and the last one will have the **proper** .dataSource property
- // TODO: Make this a singleton and turn off events when moving to another page
- })(this), 1000);
- }
- if (this.cachedData != null) {
- return this.loadData(this.cachedData);
- }
+
+ if (this.dataSource && !this.dataLoading && !this.cachedData) {
+ this.dataLoading = true;
+ return this.fetchData(this.dataSource)
+ .done((data) => {
+ this.dataLoading = false;
+ this.loadData(data);
+ });
+ };
+
+ if (this.cachedData != null) {
+ return this.loadData(this.cachedData);
}
- },
+ }, 1000),
setupAtWho: function() {
// Emoji
this.input.atwho({
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index d4403375643..53762f2965c 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -25,7 +25,7 @@
return function(e) {
e.preventDefault();
e.stopPropagation();
- return _this.input.val('').trigger('keyup').focus();
+ return _this.input.val('').trigger('input').focus();
};
})(this));
// Key events
@@ -37,28 +37,16 @@
e.preventDefault()
}
})
- .on('keyup', function(e) {
- var keyCode;
- keyCode = e.which;
- if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
- return;
- }
+ .on('input', function() {
if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.addClass(HAS_VALUE_CLASS);
} else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS);
}
- if (keyCode === 13 && !options.elIsInput) {
- return false;
- }
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
clearTimeout(timeout);
return timeout = setTimeout(function() {
- var blurField = this.shouldBlur(keyCode);
- if (blurField && this.filterInputBlur) {
- this.input.blur();
- }
return this.options.query(this.input.val(), function(data) {
return this.options.callback(data);
}.bind(this));
@@ -255,7 +243,7 @@
_this.fullData = data;
_this.parseData(_this.fullData);
if (_this.options.filterable && _this.filter && _this.filter.input) {
- return _this.filter.input.trigger('keyup');
+ return _this.filter.input.trigger('input');
}
};
// Remote data
@@ -487,7 +475,7 @@
// Triggering 'keyup' will re-render the dropdown which is not always required
// specially if we want to keep the state of the dropdown needed for bulk-assignment
if (!this.options.persistWhenHide) {
- $input.trigger("keyup");
+ $input.trigger("input");
}
if (this.dropdown.find(".dropdown-toggle-page").length) {
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
@@ -500,14 +488,27 @@
// Render the full menu
GitLabDropdown.prototype.renderMenu = function(html) {
- var menu_html;
- menu_html = "";
if (this.options.renderMenu) {
- menu_html = this.options.renderMenu(html);
+ return this.options.renderMenu(html);
} else {
- menu_html = $('<ul />').append(html);
+ var ul = document.createElement('ul');
+
+ for (var i = 0; i < html.length; i++) {
+ var el = html[i];
+
+ if (el instanceof jQuery) {
+ el = el.get(0);
+ }
+
+ if (typeof el === 'string') {
+ ul.innerHTML += el;
+ } else {
+ ul.appendChild(el);
+ }
+ }
+
+ return ul;
}
- return menu_html;
};
// Append the menu into the dropdown
@@ -521,7 +522,7 @@
};
GitLabDropdown.prototype.renderItem = function(data, group, index) {
- var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value;
+ var field, fieldName, html, selected, text, url, value;
if (group == null) {
group = false;
}
@@ -529,18 +530,16 @@
// Render the row
index = false;
}
- html = "";
- // Divider
- if (data === "divider") {
- return "<li class='divider'></li>";
- }
- // Separator is a full-width divider
- if (data === "separator") {
- return "<li class='separator'></li>";
+ html = document.createElement('li');
+ if (data === 'divider' || data === 'separator') {
+ html.className = data;
+ return html;
}
// Header
if (data.header != null) {
- return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
+ html.className = 'dropdown-header';
+ html.innerHTML = data.header;
+ return html;
}
if (this.options.renderRow) {
// Call the render function
@@ -567,24 +566,25 @@
} else {
text = data.text != null ? data.text : '';
}
- cssClass = "";
- if (selected) {
- cssClass = "is-active";
- }
if (this.highlight) {
text = this.highlightTextMatches(text, this.filterInput.val());
}
+ // Create the list item & the link
+ var link = document.createElement('a');
+
+ link.href = url;
+ link.innerHTML = text;
+
+ if (selected) {
+ link.className = 'is-active';
+ }
+
if (group) {
- groupAttrs = 'data-group=' + group + ' data-index=' + index;
- } else {
- groupAttrs = '';
+ link.dataset.group = group;
+ link.dataset.index = index;
}
- html = _.template('<li><a href="<%- url %>" <%- groupAttrs %> class="<%- cssClass %>"><%= text %></a></li>')({
- url: url,
- groupAttrs: groupAttrs,
- cssClass: cssClass,
- text: text
- });
+
+ html.appendChild(link);
}
return html;
};
@@ -738,6 +738,7 @@
return false;
}
if (currentKeyCode === 13 && currentIndex !== -1) {
+ e.preventDefault();
_this.selectRowAtIndex();
}
};
diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6
new file mode 100644
index 00000000000..8657e7b4abf
--- /dev/null
+++ b/app/assets/javascripts/gl_field_errors.js.es6
@@ -0,0 +1,167 @@
+((global) => {
+ /*
+ * This class overrides the browser's validation error bubbles, displaying custom
+ * error messages for invalid fields instead. To begin validating any form, add the
+ * class `show-gl-field-errors` to the form element, and ensure error messages are
+ * declared in each inputs' title attribute.
+ *
+ * Example:
+ *
+ * <form class='show-gl-field-errors'>
+ * <input type='text' name='username' title='Username is required.'/>
+ *</form>
+ *
+ * */
+
+ const errorMessageClass = 'gl-field-error';
+ const inputErrorClass = 'gl-field-error-outline';
+
+ class GlFieldError {
+ constructor({ input, formErrors }) {
+ this.inputElement = $(input);
+ this.inputDomElement = this.inputElement.get(0);
+ this.form = formErrors;
+ this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
+ this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
+
+ this.state = {
+ valid: false,
+ empty: true
+ };
+
+ this.initFieldValidation();
+ }
+
+ initFieldValidation() {
+ // hidden when injected into DOM
+ this.inputElement.after(this.fieldErrorElement);
+ this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
+ this.scopedSiblings = this.safelySelectSiblings();
+ }
+
+ safelySelectSiblings() {
+ // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
+ const ignoreSelector = '.validation-ignore';
+ const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
+ const parentContainer = this.inputElement.parent('.form-group');
+
+ // Only select siblings when they're scoped within a form-group with one input
+ const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
+
+ return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
+ }
+
+ renderValidity() {
+ this.renderClear();
+
+ if (this.state.valid) {
+ return this.renderValid();
+ }
+
+ if (this.state.empty) {
+ return this.renderEmpty();
+ }
+
+ if (!this.state.valid) {
+ return this.renderInvalid();
+ }
+
+ }
+
+ handleInvalidSubmit(event) {
+ event.preventDefault();
+ const currentValue = this.accessCurrentValue();
+ this.state.valid = false;
+ this.state.empty = currentValue === '';
+
+ this.renderValidity();
+ this.form.focusOnFirstInvalid.apply(this.form);
+ // For UX, wait til after first invalid submission to check each keyup
+ this.inputElement.off('keyup.field_validator')
+ .on('keyup.field_validator', this.updateValidity.bind(this));
+
+ }
+
+ /* Get or set current input value */
+ accessCurrentValue(newVal) {
+ return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
+ }
+
+ getInputValidity() {
+ return this.inputDomElement.validity.valid;
+ }
+
+ updateValidity() {
+ const inputVal = this.accessCurrentValue();
+ this.state.empty = !inputVal.length;
+ this.state.valid = this.getInputValidity();
+ this.renderValidity();
+ }
+
+ renderValid() {
+ return this.renderClear();
+ }
+
+ renderEmpty() {
+ return this.renderInvalid();
+ }
+
+ renderInvalid() {
+ this.inputElement.addClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ return this.fieldErrorElement.show();
+ }
+
+ renderClear() {
+ const inputVal = this.accessCurrentValue();
+ if (!inputVal.split(' ').length) {
+ const trimmedInput = inputVal.trim();
+ this.accessCurrentValue(trimmedInput);
+ }
+ this.inputElement.removeClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ this.fieldErrorElement.hide();
+ }
+ }
+
+ const customValidationFlag = 'no-gl-field-errors';
+
+ class GlFieldErrors {
+ constructor(form) {
+ this.form = $(form);
+ this.state = {
+ inputs: [],
+ valid: false
+ };
+ this.initValidators();
+ }
+
+ initValidators () {
+ // select all non-hidden inputs in form
+ this.state.inputs = this.form.find(':input:not([type=hidden])').toArray()
+ .filter((input) => !input.classList.contains(customValidationFlag))
+ .map((input) => new GlFieldError({ input, formErrors: this }));
+
+ this.form.on('submit', this.catchInvalidFormSubmit);
+ }
+
+ /* Neccessary to prevent intercept and override invalid form submit
+ * because Safari & iOS quietly allow form submission when form is invalid
+ * and prevents disabling of invalid submit button by application.js */
+
+ catchInvalidFormSubmit (event) {
+ if (!event.currentTarget.checkValidity()) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ focusOnFirstInvalid () {
+ const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
+ firstInvalid.inputElement.focus();
+ }
+ }
+
+ global.GlFieldErrors = GlFieldErrors;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js
deleted file mode 100644
index 4382dd6860f..00000000000
--- a/app/assets/javascripts/groups.js
+++ /dev/null
@@ -1,13 +0,0 @@
-(function() {
- this.GroupMembers = (function() {
- function GroupMembers() {
- $('li.group_member').bind('ajax:success', function() {
- return $(this).fadeOut();
- });
- }
-
- return GroupMembers;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6
index 0808f538f01..0808f538f01 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js.es6
+++ b/app/assets/javascripts/issues_bulk_assignment.js.es6
diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index bc68e53504f..bc68e53504f 100644
--- a/app/assets/javascripts/LabelManager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index e356872624a..f1e719937c7 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -292,7 +292,7 @@
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
return;
}
if ($dropdown.hasClass('js-multiselect')) {
@@ -334,7 +334,7 @@
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
}
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 9299d0eabd2..b170e26eebf 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -38,6 +38,11 @@
gl.utils.getPagePath = function() {
return $('body').data('page').split(':')[0];
};
+ gl.utils.parseUrl = function (url) {
+ var parser = document.createElement('a');
+ parser.href = url;
+ return parser;
+ };
return jQuery.timefor = function(time, suffix, expiredLabel) {
var suffixFromNow, timefor;
if (!time) {
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 1935af491f7..e1532fd9ec4 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -14,14 +14,18 @@
inputs.datepicker({
dateFormat: 'yy-mm-dd',
minDate: 1,
- onSelect: toggleClearInput
+ onSelect: function () {
+ $(this).trigger('change');
+ toggleClearInput.call(this);
+ }
});
inputs.next('.js-clear-input').on('click', function(event) {
event.preventDefault();
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
- input.datepicker('setDate', null);
+ input.datepicker('setDate', null)
+ .trigger('change');
toggleClearInput.call(input);
});
diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6
new file mode 100644
index 00000000000..a0cd20f21e8
--- /dev/null
+++ b/app/assets/javascripts/members.js.es6
@@ -0,0 +1,36 @@
+((w) => {
+ w.gl = w.gl || {};
+
+ class Members {
+ constructor() {
+ this.addListeners();
+ }
+
+ addListeners() {
+ $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
+ $('.js-member-update-control').off('change').on('change', this.formSubmit);
+ $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
+ }
+
+ removeRow(e) {
+ const $target = $(e.target);
+
+ if ($target.hasClass('btn-remove')) {
+ $target.closest('.member')
+ .fadeOut(function () {
+ $(this).remove();
+ });
+ }
+ }
+
+ formSubmit() {
+ $(this).closest('form').trigger("submit.rails").end().disable();
+ }
+
+ formSuccess() {
+ $(this).find('.js-member-update-control').enable();
+ }
+ }
+
+ gl.Members = Members;
+})(window);
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index bec11a198a1..fd21aa1fefa 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -61,6 +61,9 @@
function MergeRequestTabs(opts) {
this.opts = opts != null ? opts : {};
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true;
+
+ this.buildsLoaded = this.opts.buildsLoaded || false;
+
this.setCurrentAction = bind(this.setCurrentAction, this);
this.tabShown = bind(this.tabShown, this);
this.showTab = bind(this.showTab, this);
@@ -68,6 +71,7 @@
this._location = location;
this.bindEvents();
this.activateTab(this.opts.action);
+ this.initAffix();
}
MergeRequestTabs.prototype.bindEvents = function() {
@@ -93,7 +97,7 @@
this.loadCommits($target.attr('href'));
this.expandView();
this.resetViewContainer();
- } else if (action === 'diffs') {
+ } else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
@@ -170,8 +174,9 @@
action = 'notes';
}
this.currentAction = action;
- // Remove a trailing '/commits' or '/diffs'
- new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
+ // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs'
+ new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, '');
+
// Append the new action if we're on a tab other than 'notes'
if (action !== 'notes') {
new_state += "/" + action;
@@ -210,8 +215,13 @@
if (this.diffsLoaded) {
return;
}
+
+ // We extract pathname for the current Changes tab anchor href
+ // some pages like MergeRequestsController#new has query parameters on that anchor
+ var url = gl.utils.parseUrl(source);
+
return this._get({
- url: (source + ".json") + this._location.search,
+ url: (url.pathname + ".json") + this._location.search,
success: (function(_this) {
return function(data) {
$('#diffs').html(data.html);
@@ -223,7 +233,7 @@
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight();
$('#diffs .diff-file').singleFileDiff();
- if (_this.diffViewType() === 'parallel' && _this.currentAction === 'diffs') {
+ if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) {
_this.expandViewContainer();
}
_this.diffsLoaded = true;
@@ -324,6 +334,10 @@
return $('.inline-parallel-buttons a.active').data('view-type');
};
+ MergeRequestTabs.prototype.isDiffAction = function(action) {
+ return action === 'diffs' || action === 'new/diffs'
+ };
+
MergeRequestTabs.prototype.expandViewContainer = function() {
var $wrapper = $('.content-wrapper .container-fluid');
if (this.fixedLayoutPref === null) {
@@ -367,6 +381,46 @@
// Only when sidebar is collapsed
};
+ MergeRequestTabs.prototype.initAffix = function () {
+ var $tabs = $('.js-tabs-affix');
+
+ // Screen space on small screens is usually very sparse
+ // So we dont affix the tabs on these
+ if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
+
+ var tabsWidth = $tabs.outerWidth(),
+ $diffTabs = $('#diff-notes-app'),
+ offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height());
+
+ $tabs.off('affix.bs.affix affix-top.bs.affix')
+ .affix({
+ offset: {
+ top: offsetTop
+ }
+ }).on('affix.bs.affix', function () {
+ $tabs.css({
+ left: $tabs.offset().left,
+ width: tabsWidth
+ });
+ $diffTabs.css({
+ marginTop: $tabs.height()
+ });
+ }).on('affix-top.bs.affix', function () {
+ $tabs.css({
+ left: '',
+ width: ''
+ });
+ $diffTabs.css({
+ marginTop: ''
+ });
+ });
+
+ // Fix bug when reloading the page already scrolling
+ if ($tabs.hasClass('affix')) {
+ $tabs.trigger('affix.bs.affix');
+ }
+ };
+
return MergeRequestTabs;
})();
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js.es6
index 7bbcdf59838..fcadc4bc515 100644
--- a/app/assets/javascripts/merge_request_widget.js
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -1,7 +1,26 @@
-(function() {
+ ((global) => {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
- this.MergeRequestWidget = (function() {
+ const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>">
+ <div class="ci_widget ci-success">
+ <%= ci_success_icon %>
+ <span>
+ Deployed to
+ <a href="<%- url %>" target="_blank" class="environment">
+ <%- name %>
+ </a>
+ <span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>">
+ <%- deployed_at %>
+ </span>
+ <a class="js-environment-link" href="<%- external_url %>" target="_blank">
+ <i class="fa fa-external-link"></i>
+ View on <%- external_url_formatted %>
+ </a>
+ </span>
+ </div>
+ </div>`;
+
+ global.MergeRequestWidget = (function() {
function MergeRequestWidget(opts) {
// Initialize MergeRequestWidget behavior
//
@@ -10,17 +29,23 @@
// ci_status_url - String, URL to use to check CI status
//
this.opts = opts;
+ this.$widgetBody = $('.mr-widget-body');
$('#modal_merge_info').modal({
show: false
});
this.firstCICheck = true;
this.readyForCICheck = false;
+ this.readyForCIEnvironmentCheck = false;
this.cancel = false;
clearInterval(this.fetchBuildStatusInterval);
+ clearInterval(this.fetchBuildEnvironmentStatusInterval);
this.clearEventListeners();
this.addEventListeners();
this.getCIStatus(false);
+ this.getCIEnvironmentsStatus();
+ this.retrieveSuccessIcon();
this.pollCIStatus();
+ this.pollCIEnvironmentsStatus();
notifyPermissions();
}
@@ -41,6 +66,7 @@
page = $('body').data('page').split(':').last();
if (allowedPages.indexOf(page) < 0) {
clearInterval(_this.fetchBuildStatusInterval);
+ clearInterval(_this.fetchBuildEnvironmentStatusInterval);
_this.cancelPolling();
return _this.clearEventListeners();
}
@@ -48,6 +74,12 @@
})(this));
};
+ MergeRequestWidget.prototype.retrieveSuccessIcon = function() {
+ const $ciSuccessIcon = $('.js-success-icon');
+ this.$ciSuccessIcon = $ciSuccessIcon.html();
+ $ciSuccessIcon.remove();
+ }
+
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
if (deleteSourceBranch == null) {
deleteSourceBranch = false;
@@ -62,7 +94,7 @@
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
return window.location.href = window.location.pathname + urlSuffix;
} else if (data.merge_error) {
- return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
+ return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
} else {
callback = function() {
return merge_request_widget.mergeInProgress(deleteSourceBranch);
@@ -118,6 +150,7 @@
if (data.status === '') {
return;
}
+ if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
_this.opts.ci_status = data.status;
_this.showCIStatus(data.status);
@@ -150,6 +183,41 @@
})(this));
};
+ MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() {
+ this.fetchBuildEnvironmentStatusInterval = setInterval(() => {
+ if (!this.readyForCIEnvironmentCheck) return;
+ this.getCIEnvironmentsStatus();
+ this.readyForCIEnvironmentCheck = false;
+ }, 300000);
+ };
+
+ MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() {
+ $.getJSON(this.opts.ci_environments_status_url, (environments) => {
+ if (this.cancel) return;
+ this.readyForCIEnvironmentCheck = true;
+ if (environments && environments.length) this.renderEnvironments(environments);
+ });
+ };
+
+ MergeRequestWidget.prototype.renderEnvironments = function(environments) {
+ for (let i = 0; i < environments.length; i++) {
+ const environment = environments[i];
+ if ($(`.mr-state-widget #${ environment.id }`).length) return;
+ const $template = $(DEPLOYMENT_TEMPLATE);
+ if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
+ if (environment.deployed_at && environment.deployed_at_formatted) {
+ environment.deployed_at = $.timeago(environment.deployed_at) + '.';
+ } else {
+ $('.js-environment-timeago', $template).remove();
+ environment.name += '.';
+ }
+ environment.ci_success_icon = this.$ciSuccessIcon;
+ const templateString = _.unescape($template[0].outerHTML);
+ const template = _.template(templateString)(environment)
+ this.$widgetBody.before(template);
+ }
+ };
+
MergeRequestWidget.prototype.showCIStatus = function(state) {
var allowed_states;
if (state == null) {
@@ -190,4 +258,4 @@
})();
-}).call(this);
+ })(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 26cc6eb0e96..cee42633c79 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -110,7 +110,7 @@
e.preventDefault();
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl();
e.preventDefault();
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch_graph.js
index 91132af273a..91132af273a 100644
--- a/app/assets/javascripts/network/branch-graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6
deleted file mode 100644
index bf33eb10100..00000000000
--- a/app/assets/javascripts/pipeline.js.es6
+++ /dev/null
@@ -1,15 +0,0 @@
-(function() {
- function toggleGraph() {
- const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
- const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
- const $btnText = $(this).find('.toggle-btn-text');
-
- $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
-
- const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
-
- graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
- }
-
- $(document).on('click', '.toggle-pipeline-btn', toggleGraph);
-})();
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
new file mode 100644
index 00000000000..6bf63ee6979
--- /dev/null
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -0,0 +1,40 @@
+((global) => {
+
+ class Pipelines {
+ constructor() {
+ $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
+ this.addMarginToBuildColumns();
+ }
+
+ toggleGraph() {
+ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
+ const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
+ const $btnText = $(this).find('.toggle-btn-text');
+ const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
+
+ $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
+
+
+ graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
+ }
+
+ addMarginToBuildColumns() {
+ const $secondChildBuildNode = $('.build:nth-child(2)');
+ if ($secondChildBuildNode.length) {
+ const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
+ const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
+ const $previousColumn = $multiBuildColumn.prev('.stage-column');
+ $multiBuildColumn.addClass('left-margin');
+ $firstChildBuildNode.addClass('left-connector');
+ $previousColumn.each(function() {
+ $this = $(this);
+ if ($('.build', $this).length === 1) $this.addClass('no-margin');
+ });
+ }
+ $('.pipeline-graph').removeClass('hidden');
+ }
+ }
+
+ global.Pipelines = Pipelines;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js
deleted file mode 100644
index 78f7b48bc7d..00000000000
--- a/app/assets/javascripts/project_members.js
+++ /dev/null
@@ -1,10 +0,0 @@
-(function() {
- this.ProjectMembers = (function() {
- function ProjectMembers() {
- $('li.project_member').bind('ajax:success', function() {
- return $(this).fadeOut();
- });
- }
- return ProjectMembers;
- })();
-}).call(this);
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index a787b11f2a9..3cf41505814 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -4,7 +4,9 @@
this.ProjectNew = (function() {
function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this);
- this.$selects = $('.features select');
+ this.$selects = $('.features select').filter(function () {
+ return $(this).data('field');
+ });
$('.project-edit-container').on('ajax:before', (function(_this) {
return function() {
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6
index 2ecf3b18975..bd4e3c3d00d 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6
@@ -16,7 +16,13 @@
if (initialQuery.name) this.requestFile(initialQuery);
$('.reset-template', this.dropdown.parent()).on('click', () => {
- if (this.currentTemplate) this.setInputValueToTemplateContent(false);
+ this.setInputValueToTemplateContent();
+ });
+
+ $('.no-template', this.dropdown.parent()).on('click', () => {
+ this.currentTemplate = '';
+ this.setInputValueToTemplateContent();
+ $('.dropdown-toggle-text', this.dropdown).text('Choose a template');
});
}
diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6
index 63bce0a6f6f..dfdfa1e7f75 100644
--- a/app/assets/javascripts/user_tabs.js.es6
+++ b/app/assets/javascripts/user_tabs.js.es6
@@ -89,7 +89,7 @@ content on the Users#show page.
const action = $target.data('action');
const source = $target.attr('href');
this.setTab(source, action);
- return this.setCurrentAction(action);
+ return this.setCurrentAction(source, action);
}
activateTab(action) {
@@ -142,14 +142,9 @@ content on the Users#show page.
.toggle(status);
}
- setCurrentAction(action) {
- const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`);
- let new_state = this._location.pathname;
+ setCurrentAction(source, action) {
+ let new_state = source
new_state = new_state.replace(/\/+$/, '');
- new_state = new_state.replace(regExp, '');
- if (action !== this.defaultAction) {
- new_state += `/${action}`;
- }
new_state += this._location.search + this._location.hash;
history.replaceState({
turbolinks: true,
diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6
new file mode 100644
index 00000000000..2517f778365
--- /dev/null
+++ b/app/assets/javascripts/username_validator.js.es6
@@ -0,0 +1,133 @@
+((global) => {
+ const debounceTimeoutDuration = 1000;
+ const invalidInputClass = 'gl-field-error-outline';
+ const successInputClass = 'gl-field-success-outline';
+ const unavailableMessageSelector = '.username .validation-error';
+ const successMessageSelector = '.username .validation-success';
+ const pendingMessageSelector = '.username .validation-pending';
+ const invalidMessageSelector = '.username .gl-field-error';
+
+ class UsernameValidator {
+ constructor() {
+ this.inputElement = $('#new_user_username');
+ this.inputDomElement = this.inputElement.get(0);
+ this.state = {
+ available: false,
+ valid: false,
+ pending: false,
+ empty: true
+ };
+
+ const debounceTimeout = _.debounce((username) => {
+ this.validateUsername(username);
+ }, debounceTimeoutDuration);
+
+ this.inputElement.on('keyup.username_check', () => {
+ const username = this.inputElement.val();
+
+ this.state.valid = this.inputDomElement.validity.valid;
+ this.state.empty = !username.length;
+
+ if (this.state.valid) {
+ return debounceTimeout(username);
+ }
+
+ this.renderState();
+ });
+
+ // Override generic field validation
+ this.inputElement.on('invalid', this.interceptInvalid.bind(this));
+ }
+
+ renderState() {
+ // Clear all state
+ this.clearFieldValidationState();
+
+ if (this.state.valid && this.state.available) {
+ return this.setSuccessState();
+ }
+
+ if (this.state.empty) {
+ return this.clearFieldValidationState();
+ }
+
+ if (this.state.pending) {
+ return this.setPendingState();
+ }
+
+ if (!this.state.available) {
+ return this.setUnavailableState();
+ }
+
+ if (!this.state.valid) {
+ return this.setInvalidState();
+ }
+ }
+
+ interceptInvalid(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ validateUsername(username) {
+ if (this.state.valid) {
+ this.state.pending = true;
+ this.state.available = false;
+ this.renderState();
+ return $.ajax({
+ type: 'GET',
+ url: `/u/${username}/exists`,
+ dataType: 'json',
+ success: (res) => this.setAvailabilityState(res.exists)
+ });
+ }
+ }
+
+ setAvailabilityState(usernameTaken) {
+ if (usernameTaken) {
+ this.state.valid = false;
+ this.state.available = false;
+ } else {
+ this.state.available = true;
+ }
+ this.state.pending = false;
+ this.renderState();
+ }
+
+ clearFieldValidationState() {
+ this.inputElement.siblings('p').hide();
+
+ this.inputElement.removeClass(invalidInputClass)
+ .removeClass(successInputClass);
+ }
+
+ setUnavailableState() {
+ const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $usernameUnavailableMessage.show();
+ }
+
+ setSuccessState() {
+ const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
+ this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
+ $usernameSuccessMessage.show();
+ }
+
+ setPendingState() {
+ const $usernamePendingMessage = $(pendingMessageSelector);
+ if (this.state.pending) {
+ $usernamePendingMessage.show();
+ } else {
+ $usernamePendingMessage.hide();
+ }
+ }
+
+ setInvalidState() {
+ const $inputErrorMessage = $(invalidMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $inputErrorMessage.show();
+ }
+ }
+
+ global.UsernameValidator = UsernameValidator;
+})(window);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 05056a73aaf..3020b7cc239 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -71,8 +71,8 @@
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
};
- collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/u/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
- assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
+ collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
+ assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
@@ -160,7 +160,7 @@
selectedId = user.id;
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl();
@@ -261,10 +261,11 @@
}
}
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
+ var trimmed = query.term.trim();
emailUser = {
name: "Invite \"" + query.term + "\"",
- username: query.term,
- id: query.term
+ username: trimmed,
+ id: trimmed
};
data.results.unshift(emailUser);
}
@@ -324,6 +325,10 @@
};
UsersSelect.prototype.user = function(user_id, callback) {
+ if(!/^\d+$/.test(user_id)) {
+ return false;
+ }
+
var url;
url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id);
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index 897bc49e7df..e3ca7f6373a 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -5,6 +5,7 @@
display: none;
&.hide { display: block; }
}
+
&.open .content {
display: block;
&.hide { display: none; }
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index c79b22d4d21..98e301d3799 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -4,7 +4,7 @@
width: 40px;
height: 40px;
padding: 0;
- @include border-radius($avatar_radius);
+ border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline {
@@ -17,7 +17,7 @@
}
&.avatar-tile {
- @include border-radius(0);
+ border-radius: 0;
border: none;
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d315db4cb32..df2e2ea8d2c 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -19,6 +19,7 @@
&.diff-collapsed {
padding: 5px;
+
.click-to-expand {
cursor: pointer;
}
@@ -133,7 +134,7 @@
}
.identicon {
- @include border-radius(50%);
+ border-radius: 50%;
}
}
@@ -203,6 +204,7 @@
}
}
}
+
&.user-cover-block {
padding: 24px 0 0;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index ce489f7c3de..e6656c2d69a 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,5 +1,5 @@
@mixin btn-default {
- @include border-radius(3px);
+ border-radius: 3px;
font-size: $gl-font-size;
font-weight: 500;
padding: $gl-vert-padding $gl-btn-padding;
@@ -8,7 +8,7 @@
&:active {
outline: none;
background-color: $btn-active-gray;
- @include box-shadow($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
}
}
@@ -25,7 +25,7 @@
&:focus {
background-color: $hover-background;
color: $hover-text;
- border-color: $hover-border;;
+ border-color: $hover-border;
}
}
@@ -43,7 +43,7 @@
&:active,
&.active {
- @include box-shadow ($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
background-color: $dark;
border-color: $border-dark;
@@ -152,7 +152,8 @@
@include btn-blue-medium;
}
- &.btn-info {
+ &.btn-info,
+ &.btn-register {
@include btn-blue;
}
@@ -194,10 +195,17 @@
pointer-events: none !important;
}
- .caret {
+ .fa-caret-down,
+ .fa-caret-up {
margin-left: 5px;
}
+ &.dropdown-toggle {
+ .fa-caret-down {
+ margin-left: 3px;
+ }
+ }
+
svg {
height: 15px;
width: 15px;
@@ -233,6 +241,7 @@
width: 100%;
margin: 0;
margin-bottom: 15px;
+
&.btn {
padding: 6px 0;
}
@@ -272,7 +281,7 @@
}
.active {
- @include box-shadow($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
@@ -314,6 +323,7 @@
.btn-build {
margin-left: 10px;
+
i {
color: $gl-icon-color;
}
@@ -321,6 +331,7 @@
.clone-dropdown-btn a {
color: $dropdown-link-color;
+
&:hover {
text-decoration: none;
}
@@ -330,6 +341,7 @@
background-color: $background-color !important;
border: 1px solid lightgrey;
cursor: default;
+
&:active {
-moz-box-shadow: inset 0 0 0 white;
-webkit-box-shadow: inset 0 0 0 white;
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index da7bab74a32..f3b6ad88ad6 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -13,10 +13,12 @@
color: $text-color;
background: $background-color;
}
+
.bs-callout h4 {
margin-top: 0;
margin-bottom: 5px;
}
+
.bs-callout p:last-child {
margin-bottom: 0;
}
@@ -27,16 +29,19 @@
border-color: #eed3d7;
color: #b94a48;
}
+
.bs-callout-warning {
background-color: #faf8f0;
border-color: #faebcc;
color: #8a6d3b;
}
+
.bs-callout-info {
background-color: #f4f8fa;
border-color: #bce8f1;
color: #34789a;
}
+
.bs-callout-success {
background-color: #dff0d8;
border-color: #5ca64d;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 5957dce89bc..81e4e264560 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -1,31 +1,31 @@
/** COLORS **/
.cgray { color: $gl-gray; }
-.clgray { color: #bbb }
+.clgray { color: #bbb; }
.cred { color: $gl-text-red; }
.cgreen { color: $gl-text-green; }
-.cdark { color: #444 }
+.cdark { color: #444; }
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; }
-.prepend-top-10 { margin-top: 10px }
+.prepend-top-10 { margin-top: 10px; }
.prepend-top-default { margin-top: $gl-padding !important; }
-.prepend-top-20 { margin-top: 20px }
-.prepend-left-5 { margin-left: 5px }
-.prepend-left-10 { margin-left: 10px }
+.prepend-top-20 { margin-top: 20px; }
+.prepend-left-5 { margin-left: 5px; }
+.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
-.prepend-left-20 { margin-left: 20px }
-.append-right-5 { margin-right: 5px }
-.append-right-10 { margin-right: 10px }
+.prepend-left-20 { margin-left: 20px; }
+.append-right-5 { margin-right: 5px; }
+.append-right-10 { margin-right: 10px; }
.append-right-default { margin-right: $gl-padding; }
-.append-right-20 { margin-right: 20px }
-.append-bottom-0 { margin-bottom: 0 }
-.append-bottom-10 { margin-bottom: 10px }
-.append-bottom-15 { margin-bottom: 15px }
-.append-bottom-20 { margin-bottom: 20px }
+.append-right-20 { margin-right: 20px; }
+.append-bottom-0 { margin-bottom: 0; }
+.append-bottom-10 { margin-bottom: 10px; }
+.append-bottom-15 { margin-bottom: 15px; }
+.append-bottom-20 { margin-bottom: 20px; }
.append-bottom-default { margin-bottom: $gl-padding; }
-.inline { display: inline-block }
-.center { text-align: center }
+.inline { display: inline-block; }
+.center { text-align: center; }
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: #999; }
@@ -97,6 +97,7 @@ span.update-author {
color: #999;
font-weight: normal;
font-style: italic;
+
strong {
font-weight: bold;
font-style: normal;
@@ -128,7 +129,7 @@ p.time {
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
- img { max-width: 100% }
+ img { max-width: 100%; }
.note-title {
li {
border-bottom: none !important;
@@ -172,6 +173,7 @@ li.note {
@extend .col-md-6;
text-align: left;
margin-top: 40px;
+
pre {
background: white;
border: none;
@@ -197,6 +199,7 @@ li.note {
background: #c67;
color: #fff;
font-weight: bold;
+
a {
color: #fff;
text-decoration: underline;
@@ -227,6 +230,7 @@ li.note {
&.milestone-closed {
background: $gray-light;
}
+
.progress {
margin-bottom: 0;
margin-top: 4px;
@@ -286,6 +290,7 @@ table {
.footer-links {
margin-bottom: 20px;
+
a {
margin-right: 15px;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 4a87a73a68a..a839371a6f2 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -1,20 +1,3 @@
-.caret {
- display: inline-block;
- width: 0;
- height: 0;
- margin-left: 2px;
- vertical-align: middle;
- border-top: $caret-width-base dashed;
- border-right: $caret-width-base solid transparent;
- border-left: $caret-width-base solid transparent;
-}
-
-.btn-group {
- .caret {
- margin-left: 0;
- }
-}
-
.dropdown {
position: relative;
@@ -29,6 +12,7 @@
.dropdown-menu,
.dropdown-menu-nav {
display: block;
+
@media (max-width: $screen-xs-max) {
width: 100%;
}
@@ -65,6 +49,7 @@
margin-top: -6px;
color: $dropdown-toggle-icon-color;
font-size: 10px;
+
&.fa-spinner {
font-size: 16px;
margin-top: -8px;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 81520500594..13c1bbf0359 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -26,15 +26,6 @@
padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
- cursor: pointer;
-
- &:hover {
- background-color: $dark-background-color;
- }
-
- .diff-toggle-caret {
- padding-right: 6px;
- }
&.file-title-clear {
padding-left: 0;
@@ -66,6 +57,7 @@
margin-top: -3px;
}
}
+
.file-content {
background: #fff;
@@ -105,22 +97,27 @@
border: none;
margin: 0;
}
+
tr {
border-bottom: 1px solid #eee;
}
+
td {
&:first-child {
border-left: none;
}
+
&:last-child {
border-right: none;
}
}
+
td.blame-commit {
padding: 0 10px;
min-width: 400px;
background: $gray-light;
}
+
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
@@ -130,6 +127,7 @@
margin-right: 0;
}
}
+
td.lines {
padding: 0;
}
@@ -146,8 +144,10 @@
border-left: 1px solid $border-color;
margin-bottom: 0;
background: white;
+
li {
color: #888;
+
p {
margin: 0;
color: #333;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 37ff7e22ed1..761c07384f4 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -9,7 +9,7 @@ input {
input[type='text'].danger {
background: #f2dede!important;
border-color: #d66;
- text-shadow: 0 1px 1px #fff
+ text-shadow: 0 1px 1px #fff;
}
.datetime-controls {
@@ -74,17 +74,17 @@ label {
.form-control {
@include box-shadow(none);
- border-radius: 3px;
+ border-radius: 2px;
padding: $gl-vert-padding $gl-input-padding;
}
.select-wrapper {
position: relative;
- .caret {
+ .fa-caret-down {
position: absolute;
right: 10px;
- top: $gl-padding;
+ top: 10px;
color: $gray-darkest;
pointer-events: none;
}
@@ -117,9 +117,11 @@ label {
display: table-cell;
width: 200px !important;
}
+
.input-group-addon {
background-color: #f7f8fa;
}
+
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
@@ -129,3 +131,8 @@ label {
.help-block {
margin-bottom: 0;
}
+
+.gl-field-error {
+ color: $red-normal;
+}
+
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 3673b81f183..fe834f4e2f6 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -62,7 +62,7 @@
}
i {
- color: $white-light
+ color: $white-light;
}
path,
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index c748f856501..3a4fdd0da22 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -57,6 +57,10 @@ header {
&:hover, &:focus, &:active {
background-color: $background-color;
}
+
+ .fa-caret-down {
+ font-size: 15px;
+ }
}
.navbar-toggle {
@@ -164,6 +168,7 @@ header {
a {
color: $gl-text-color;
+
&:hover {
text-decoration: underline;
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 8bfc0d583c5..ba3930e03bd 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -16,7 +16,7 @@
margin-top: 5px;
}
- @include border-radius(3px);
+ border-radius: 3px;
display: block;
float: left;
margin-right: 10px;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index efc348214c2..4b2627c1b87 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -60,6 +60,7 @@
padding-top: 1px;
margin: 0;
color: $gray-dark;
+
img {
position: relative;
top: 3px;
@@ -128,6 +129,10 @@ ul.content-list {
color: $gl-dark-link-color;
}
+ .member-group-link {
+ color: $blue-normal;
+ }
+
.description {
p {
@include str-truncated;
@@ -168,6 +173,14 @@ ul.content-list {
}
}
+ .member-controls {
+ float: none;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ }
+ }
+
// When dragging a list item
&.ui-sortable-helper {
border-bottom: none;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
index 3ee3fb4cee5..a90e45bb5f4 100644
--- a/app/assets/stylesheets/framework/logo.scss
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -1,15 +1,3 @@
-@mixin unique-keyframes {
- $animation-name: unique-id();
- @include webkit-prefix(animation-name, $animation-name);
-
- @-webkit-keyframes #{$animation-name} {
- @content;
- }
- @keyframes #{$animation-name} {
- @content;
- }
-}
-
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
@@ -20,28 +8,6 @@
}
}
-@mixin tanuki-second-highlight-animations($tanuki-color) {
- @include unique-keyframes {
- 10%, 80% {
- fill: #{$tanuki-color}
- }
- 20%, 90% {
- fill: lighten($tanuki-color, 25%);
- }
- }
-}
-
-@mixin tanuki-forth-highlight-animations($tanuki-color) {
- @include unique-keyframes {
- 30%, 60% {
- fill: #{$tanuki-color};
- }
- 40%, 70% {
- fill: lighten($tanuki-color, 25%);
- }
- }
-}
-
.tanuki-logo {
.tanuki-left-ear,
@@ -67,10 +33,11 @@
}
.tanuki-left-cheek {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-left-cheek) {
0%, 10%, 100% {
fill: lighten($tanuki-yellow, 25%);
}
+
90% {
fill: $tanuki-yellow;
}
@@ -78,18 +45,35 @@
}
.tanuki-left-eye {
- @include tanuki-second-highlight-animations($tanuki-orange);
+ @include include-keyframes(animate-tanuki-left-eye) {
+ 10%, 80% {
+ fill: $tanuki-orange;
+ }
+
+ 20%, 90% {
+ fill: lighten($tanuki-orange, 25%);
+ }
+ }
}
.tanuki-left-ear {
- @include tanuki-second-highlight-animations($tanuki-red);
+ @include include-keyframes(animate-tanuki-left-ear) {
+ 10%, 80% {
+ fill: $tanuki-red;
+ }
+
+ 20%, 90% {
+ fill: lighten($tanuki-red, 25%);
+ }
+ }
}
.tanuki-nose {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-nose) {
20%, 70% {
fill: $tanuki-red;
}
+
30%, 80% {
fill: lighten($tanuki-red, 25%);
}
@@ -97,22 +81,39 @@
}
.tanuki-right-eye {
- @include tanuki-forth-highlight-animations($tanuki-orange);
+ @include include-keyframes(animate-tanuki-right-eye) {
+ 30%, 60% {
+ fill: $tanuki-orange;
+ }
+
+ 40%, 70% {
+ fill: lighten($tanuki-orange, 25%);
+ }
+ }
}
.tanuki-right-ear {
- @include tanuki-forth-highlight-animations($tanuki-red);
+ @include include-keyframes(animate-tanuki-right-ear) {
+ 30%, 60% {
+ fill: $tanuki-red;
+ }
+
+ 40%, 70% {
+ fill: lighten($tanuki-red, 25%);
+ }
+ }
}
.tanuki-right-cheek {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-right-cheek) {
40% {
fill: $tanuki-yellow;
}
+
60% {
fill: lighten($tanuki-yellow, 25%);
}
}
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index edea4ad00eb..6d28d98b283 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -86,7 +86,7 @@
}
.markdown-area {
- @include border-radius(0);
+ border-radius: 0;
background: #fff;
border: 1px solid #ddd;
min-height: 140px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1ec08cdef23..f84ca36d10f 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -1,15 +1,4 @@
/**
- * Generic mixins
- */
-@mixin box-shadow($shadow) {
- box-shadow: $shadow;
-}
-
-@mixin border-radius($radius) {
- border-radius: $radius;
-}
-
-/**
* Prefilled mixins
* Mixins with fixed values
*/
@@ -45,6 +34,7 @@
&.active {
background: $gray-light;
+
a {
font-weight: 600;
}
@@ -95,3 +85,10 @@
@content;
}
}
+
+@mixin include-keyframes($animation-name) {
+ @include webkit-prefix(animation-name, $animation-name);
+ @include keyframes($animation-name) {
+ @content;
+ }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 76b93b23b95..9fe390eb09d 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -133,5 +133,5 @@
font-size: 20px;
color: #777;
z-index: 100;
- @include box-shadow(0 1px 2px #ddd);
+ box-shadow: 0 1px 2px #ddd;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index ea43f4afc37..899db045b74 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -210,6 +210,7 @@
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;
+
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
margin: 0 0 10px;
display: block;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index c6f30e144fd..5ba0486177f 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -13,6 +13,11 @@
.dropdown-menu-toggle {
line-height: 20px;
}
+
+ .badge {
+ margin-top: -2px;
+ margin-left: 5px;
+ }
}
.panel-body {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index c75dacf95d9..e0708c65695 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -21,7 +21,14 @@
padding-right: 10px;
b {
- @extend .caret;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: $caret-width-base dashed;
+ border-right: $caret-width-base solid transparent;
+ border-left: $caret-width-base solid transparent;
color: $gray-darkest;
}
}
@@ -39,8 +46,8 @@
}
.select2-drop {
- @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
- @include border-radius ($border-radius-default);
+ box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
+ border-radius: $border-radius-default;
border: none;
min-width: 175px;
}
@@ -65,7 +72,7 @@
.select2-container-active {
.select2-choice, .select2-choices {
- @include box-shadow(none);
+ box-shadow: none;
}
}
@@ -75,18 +82,18 @@
outline: 0;
background-image: none;
background-color: $white-dark;
- @include box-shadow($gl-btn-active-gradient);
+ box-shadow: $gl-btn-active-gradient;
}
}
.select2-container-multi {
.select2-choices {
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
border-color: $input-border;
background: none;
.select2-search-field input {
- padding: $gl-padding / 2;
+ padding: 5px $gl-padding / 2;
font-size: 13px;
height: auto;
font-family: inherit;
@@ -94,7 +101,7 @@
}
.select2-search-choice {
- margin: 8px 0 0 8px;
+ margin: 5px 0 0 8px;
box-shadow: none;
border-color: $input-border;
color: $gl-text-color;
@@ -116,7 +123,7 @@
&.select2-container-active .select2-choices,
&.select2-dropdown-open .select2-choices {
border-color: $border-white-normal;
- @include box-shadow($gl-btn-active-gradient);
+ box-shadow: $gl-btn-active-gradient;
}
}
@@ -130,6 +137,7 @@
.select2-results {
max-height: 350px;
+
.select2-highlighted {
background: $gl-primary;
}
@@ -150,7 +158,7 @@
background-repeat: no-repeat;
background-position: right 0 bottom 6px;
border: 1px solid $input-border;
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
@@ -205,9 +213,11 @@
.group-image {
float: left;
}
+
.group-name {
font-weight: bold;
}
+
.group-path {
color: #999;
}
@@ -232,6 +242,7 @@
color: #aaa;
font-weight: normal;
}
+
.namespace-path {
margin-left: 10px;
font-weight: bolder;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 557ef7291cf..ec52f326eb9 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -4,7 +4,7 @@
&.page-sidebar-pinned {
.sidebar-wrapper {
- @include box-shadow(none);
+ box-shadow: none;
}
}
@@ -17,7 +17,7 @@
width: 0;
overflow: hidden;
transition: width $sidebar-transition-duration;
- @include box-shadow(2px 0 16px 0 $black-transparent);
+ box-shadow: 2px 0 16px 0 $black-transparent;
}
}
@@ -100,7 +100,7 @@
.count {
float: right;
padding: 0 8px;
- @include border-radius(6px);
+ border-radius: 6px;
}
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 0b0bd80c326..eb63a9f214b 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -48,6 +48,7 @@
&:before {
background: none;
}
+
.timeline-entry .timeline-entry-inner {
.timeline-icon {
display: none;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index e3154657c54..f4106641269 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -48,31 +48,40 @@
.clearfix {
@include clearfix();
}
+
.center-block {
@include center-block();
}
+
.pull-right {
float: right !important;
}
+
.pull-left {
float: left !important;
}
+
.hide {
display: none;
}
+
.show {
display: block !important;
}
+
.invisible {
visibility: hidden;
}
+
.text-hide {
@include text-hide();
}
+
.hidden {
display: none !important;
visibility: hidden !important;
}
+
.affix {
position: fixed;
}
@@ -146,6 +155,7 @@
padding: 6px 15px;
font-size: 13px;
font-weight: normal;
+
a {
color: #777;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 9f2d53d5206..8df0067fac1 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -106,6 +106,7 @@
@extend .table-bordered;
margin: 12px 0;
color: #5c5d5e;
+
th {
background: #f8fafc;
}
@@ -116,7 +117,7 @@
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
- @include border-radius(2px);
+ border-radius: 2px;
}
p > code {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 14ec310de2d..4c34ed3ebf7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -17,8 +17,10 @@ $white-normal: #ededed;
$white-dark: #ececec;
$gray-light: #fafafa;
+$gray-lighter: #f9f9f9;
$gray-normal: #f5f5f5;
$gray-dark: #ededed;
+$gray-darker: #eee;
$gray-darkest: #c9c9c9;
$green-light: #38ae67;
@@ -33,6 +35,8 @@ $blue-medium-light: #3498cb;
$blue-medium: #2f8ebf;
$blue-medium-dark: #2d86b4;
+$blue-light-transparent: rgba(44, 159, 216, 0.05);
+
$orange-light: #fc8a51;
$orange-normal: #e75e40;
$orange-dark: #ce5237;
@@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f;
$gl-font-size: 15px;
$gl-title-color: #333;
$gl-text-color: #5c5c5c;
+$gl-text-color-light: #8c8c8c;
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 16ffbe57a99..a3acee299e3 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -55,68 +55,68 @@
color: #000 !important;
}
- .hll { background-color: #373b41 }
- .c { color: #969896 } /* Comment */
- .err { color: #c66 } /* Error */
- .k { color: #b294bb } /* Keyword */
- .l { color: #de935f } /* Literal */
- .n { color: #c5c8c6 } /* Name */
- .o { color: #8abeb7 } /* Operator */
- .p { color: #c5c8c6 } /* Punctuation */
- .cm { color: #969896 } /* Comment.Multiline */
- .cp { color: #969896 } /* Comment.Preproc */
- .c1 { color: #969896 } /* Comment.Single */
- .cs { color: #969896 } /* Comment.Special */
- .gd { color: #c66 } /* Generic.Deleted */
- .ge { font-style: italic } /* Generic.Emph */
- .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */
- .gi { color: #b5bd68 } /* Generic.Inserted */
- .gp { color: #969896; font-weight: bold } /* Generic.Prompt */
- .gs { font-weight: bold } /* Generic.Strong */
- .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */
- .kc { color: #b294bb } /* Keyword.Constant */
- .kd { color: #b294bb } /* Keyword.Declaration */
- .kn { color: #8abeb7 } /* Keyword.Namespace */
- .kp { color: #b294bb } /* Keyword.Pseudo */
- .kr { color: #b294bb } /* Keyword.Reserved */
- .kt { color: #f0c674 } /* Keyword.Type */
- .ld { color: #b5bd68 } /* Literal.Date */
- .m { color: #de935f } /* Literal.Number */
- .s { color: #b5bd68 } /* Literal.String */
- .na { color: #81a2be } /* Name.Attribute */
- .nb { color: #c5c8c6 } /* Name.Builtin */
- .nc { color: #f0c674 } /* Name.Class */
- .no { color: #c66 } /* Name.Constant */
- .nd { color: #8abeb7 } /* Name.Decorator */
- .ni { color: #c5c8c6 } /* Name.Entity */
- .ne { color: #c66 } /* Name.Exception */
- .nf { color: #81a2be } /* Name.Function */
- .nl { color: #c5c8c6 } /* Name.Label */
- .nn { color: #f0c674 } /* Name.Namespace */
- .nx { color: #81a2be } /* Name.Other */
- .py { color: #c5c8c6 } /* Name.Property */
- .nt { color: #8abeb7 } /* Name.Tag */
- .nv { color: #c66 } /* Name.Variable */
- .ow { color: #8abeb7 } /* Operator.Word */
- .w { color: #c5c8c6 } /* Text.Whitespace */
- .mf { color: #de935f } /* Literal.Number.Float */
- .mh { color: #de935f } /* Literal.Number.Hex */
- .mi { color: #de935f } /* Literal.Number.Integer */
- .mo { color: #de935f } /* Literal.Number.Oct */
- .sb { color: #b5bd68 } /* Literal.String.Backtick */
- .sc { color: #c5c8c6 } /* Literal.String.Char */
- .sd { color: #969896 } /* Literal.String.Doc */
- .s2 { color: #b5bd68 } /* Literal.String.Double */
- .se { color: #de935f } /* Literal.String.Escape */
- .sh { color: #b5bd68 } /* Literal.String.Heredoc */
- .si { color: #de935f } /* Literal.String.Interpol */
- .sx { color: #b5bd68 } /* Literal.String.Other */
- .sr { color: #b5bd68 } /* Literal.String.Regex */
- .s1 { color: #b5bd68 } /* Literal.String.Single */
- .ss { color: #b5bd68 } /* Literal.String.Symbol */
- .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */
- .vc { color: #c66 } /* Name.Variable.Class */
- .vg { color: #c66 } /* Name.Variable.Global */
- .vi { color: #c66 } /* Name.Variable.Instance */
- .il { color: #de935f } /* Literal.Number.Integer.Long */
+ .hll { background-color: #373b41; }
+ .c { color: #969896; } /* Comment */
+ .err { color: #c66; } /* Error */
+ .k { color: #b294bb; } /* Keyword */
+ .l { color: #de935f; } /* Literal */
+ .n { color: #c5c8c6; } /* Name */
+ .o { color: #8abeb7; } /* Operator */
+ .p { color: #c5c8c6; } /* Punctuation */
+ .cm { color: #969896; } /* Comment.Multiline */
+ .cp { color: #969896; } /* Comment.Preproc */
+ .c1 { color: #969896; } /* Comment.Single */
+ .cs { color: #969896; } /* Comment.Special */
+ .gd { color: #c66; } /* Generic.Deleted */
+ .ge { font-style: italic; } /* Generic.Emph */
+ .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */
+ .gi { color: #b5bd68; } /* Generic.Inserted */
+ .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */
+ .gs { font-weight: bold; } /* Generic.Strong */
+ .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */
+ .kc { color: #b294bb; } /* Keyword.Constant */
+ .kd { color: #b294bb; } /* Keyword.Declaration */
+ .kn { color: #8abeb7; } /* Keyword.Namespace */
+ .kp { color: #b294bb; } /* Keyword.Pseudo */
+ .kr { color: #b294bb; } /* Keyword.Reserved */
+ .kt { color: #f0c674; } /* Keyword.Type */
+ .ld { color: #b5bd68; } /* Literal.Date */
+ .m { color: #de935f; } /* Literal.Number */
+ .s { color: #b5bd68; } /* Literal.String */
+ .na { color: #81a2be; } /* Name.Attribute */
+ .nb { color: #c5c8c6; } /* Name.Builtin */
+ .nc { color: #f0c674; } /* Name.Class */
+ .no { color: #c66; } /* Name.Constant */
+ .nd { color: #8abeb7; } /* Name.Decorator */
+ .ni { color: #c5c8c6; } /* Name.Entity */
+ .ne { color: #c66; } /* Name.Exception */
+ .nf { color: #81a2be; } /* Name.Function */
+ .nl { color: #c5c8c6; } /* Name.Label */
+ .nn { color: #f0c674; } /* Name.Namespace */
+ .nx { color: #81a2be; } /* Name.Other */
+ .py { color: #c5c8c6; } /* Name.Property */
+ .nt { color: #8abeb7; } /* Name.Tag */
+ .nv { color: #c66; } /* Name.Variable */
+ .ow { color: #8abeb7; } /* Operator.Word */
+ .w { color: #c5c8c6; } /* Text.Whitespace */
+ .mf { color: #de935f; } /* Literal.Number.Float */
+ .mh { color: #de935f; } /* Literal.Number.Hex */
+ .mi { color: #de935f; } /* Literal.Number.Integer */
+ .mo { color: #de935f; } /* Literal.Number.Oct */
+ .sb { color: #b5bd68; } /* Literal.String.Backtick */
+ .sc { color: #c5c8c6; } /* Literal.String.Char */
+ .sd { color: #969896; } /* Literal.String.Doc */
+ .s2 { color: #b5bd68; } /* Literal.String.Double */
+ .se { color: #de935f; } /* Literal.String.Escape */
+ .sh { color: #b5bd68; } /* Literal.String.Heredoc */
+ .si { color: #de935f; } /* Literal.String.Interpol */
+ .sx { color: #b5bd68; } /* Literal.String.Other */
+ .sr { color: #b5bd68; } /* Literal.String.Regex */
+ .s1 { color: #b5bd68; } /* Literal.String.Single */
+ .ss { color: #b5bd68; } /* Literal.String.Symbol */
+ .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */
+ .vc { color: #c66; } /* Name.Variable.Class */
+ .vg { color: #c66; } /* Name.Variable.Global */
+ .vi { color: #c66; } /* Name.Variable.Instance */
+ .il { color: #de935f; } /* Literal.Number.Integer.Long */
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 7de920e074b..e9228c94db9 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -55,65 +55,65 @@
color: #000 !important;
}
- .hll { background-color: #49483e }
- .c { color: #75715e } /* Comment */
- .err { color: #960050; background-color: #1e0010 } /* Error */
- .k { color: #66d9ef } /* Keyword */
- .l { color: #ae81ff } /* Literal */
- .n { color: #f8f8f2 } /* Name */
- .o { color: #f92672 } /* Operator */
- .p { color: #f8f8f2 } /* Punctuation */
- .cm { color: #75715e } /* Comment.Multiline */
- .cp { color: #75715e } /* Comment.Preproc */
- .c1 { color: #75715e } /* Comment.Single */
- .cs { color: #75715e } /* Comment.Special */
- .ge { font-style: italic } /* Generic.Emph */
- .gs { font-weight: bold } /* Generic.Strong */
- .kc { color: #66d9ef } /* Keyword.Constant */
- .kd { color: #66d9ef } /* Keyword.Declaration */
- .kn { color: #f92672 } /* Keyword.Namespace */
- .kp { color: #66d9ef } /* Keyword.Pseudo */
- .kr { color: #66d9ef } /* Keyword.Reserved */
- .kt { color: #66d9ef } /* Keyword.Type */
- .ld { color: #e6db74 } /* Literal.Date */
- .m { color: #ae81ff } /* Literal.Number */
- .s { color: #e6db74 } /* Literal.String */
- .na { color: #a6e22e } /* Name.Attribute */
- .nb { color: #f8f8f2 } /* Name.Builtin */
- .nc { color: #a6e22e } /* Name.Class */
- .no { color: #66d9ef } /* Name.Constant */
- .nd { color: #a6e22e } /* Name.Decorator */
- .ni { color: #f8f8f2 } /* Name.Entity */
- .ne { color: #a6e22e } /* Name.Exception */
- .nf { color: #a6e22e } /* Name.Function */
- .nl { color: #f8f8f2 } /* Name.Label */
- .nn { color: #f8f8f2 } /* Name.Namespace */
- .nx { color: #a6e22e } /* Name.Other */
- .py { color: #f8f8f2 } /* Name.Property */
- .nt { color: #f92672 } /* Name.Tag */
- .nv { color: #f8f8f2 } /* Name.Variable */
- .ow { color: #f92672 } /* Operator.Word */
- .w { color: #f8f8f2 } /* Text.Whitespace */
- .mf { color: #ae81ff } /* Literal.Number.Float */
- .mh { color: #ae81ff } /* Literal.Number.Hex */
- .mi { color: #ae81ff } /* Literal.Number.Integer */
- .mo { color: #ae81ff } /* Literal.Number.Oct */
- .sb { color: #e6db74 } /* Literal.String.Backtick */
- .sc { color: #e6db74 } /* Literal.String.Char */
- .sd { color: #e6db74 } /* Literal.String.Doc */
- .s2 { color: #e6db74 } /* Literal.String.Double */
- .se { color: #ae81ff } /* Literal.String.Escape */
- .sh { color: #e6db74 } /* Literal.String.Heredoc */
- .si { color: #e6db74 } /* Literal.String.Interpol */
- .sx { color: #e6db74 } /* Literal.String.Other */
- .sr { color: #e6db74 } /* Literal.String.Regex */
- .s1 { color: #e6db74 } /* Literal.String.Single */
- .ss { color: #e6db74 } /* Literal.String.Symbol */
- .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
- .vc { color: #f8f8f2 } /* Name.Variable.Class */
- .vg { color: #f8f8f2 } /* Name.Variable.Global */
- .vi { color: #f8f8f2 } /* Name.Variable.Instance */
- .il { color: #ae81ff } /* Literal.Number.Integer.Long */
+ .hll { background-color: #49483e; }
+ .c { color: #75715e; } /* Comment */
+ .err { color: #960050; background-color: #1e0010; } /* Error */
+ .k { color: #66d9ef; } /* Keyword */
+ .l { color: #ae81ff; } /* Literal */
+ .n { color: #f8f8f2; } /* Name */
+ .o { color: #f92672; } /* Operator */
+ .p { color: #f8f8f2; } /* Punctuation */
+ .cm { color: #75715e; } /* Comment.Multiline */
+ .cp { color: #75715e; } /* Comment.Preproc */
+ .c1 { color: #75715e; } /* Comment.Single */
+ .cs { color: #75715e; } /* Comment.Special */
+ .ge { font-style: italic; } /* Generic.Emph */
+ .gs { font-weight: bold; } /* Generic.Strong */
+ .kc { color: #66d9ef; } /* Keyword.Constant */
+ .kd { color: #66d9ef; } /* Keyword.Declaration */
+ .kn { color: #f92672; } /* Keyword.Namespace */
+ .kp { color: #66d9ef; } /* Keyword.Pseudo */
+ .kr { color: #66d9ef; } /* Keyword.Reserved */
+ .kt { color: #66d9ef; } /* Keyword.Type */
+ .ld { color: #e6db74; } /* Literal.Date */
+ .m { color: #ae81ff; } /* Literal.Number */
+ .s { color: #e6db74; } /* Literal.String */
+ .na { color: #a6e22e; } /* Name.Attribute */
+ .nb { color: #f8f8f2; } /* Name.Builtin */
+ .nc { color: #a6e22e; } /* Name.Class */
+ .no { color: #66d9ef; } /* Name.Constant */
+ .nd { color: #a6e22e; } /* Name.Decorator */
+ .ni { color: #f8f8f2; } /* Name.Entity */
+ .ne { color: #a6e22e; } /* Name.Exception */
+ .nf { color: #a6e22e; } /* Name.Function */
+ .nl { color: #f8f8f2; } /* Name.Label */
+ .nn { color: #f8f8f2; } /* Name.Namespace */
+ .nx { color: #a6e22e; } /* Name.Other */
+ .py { color: #f8f8f2; } /* Name.Property */
+ .nt { color: #f92672; } /* Name.Tag */
+ .nv { color: #f8f8f2; } /* Name.Variable */
+ .ow { color: #f92672; } /* Operator.Word */
+ .w { color: #f8f8f2; } /* Text.Whitespace */
+ .mf { color: #ae81ff; } /* Literal.Number.Float */
+ .mh { color: #ae81ff; } /* Literal.Number.Hex */
+ .mi { color: #ae81ff; } /* Literal.Number.Integer */
+ .mo { color: #ae81ff; } /* Literal.Number.Oct */
+ .sb { color: #e6db74; } /* Literal.String.Backtick */
+ .sc { color: #e6db74; } /* Literal.String.Char */
+ .sd { color: #e6db74; } /* Literal.String.Doc */
+ .s2 { color: #e6db74; } /* Literal.String.Double */
+ .se { color: #ae81ff; } /* Literal.String.Escape */
+ .sh { color: #e6db74; } /* Literal.String.Heredoc */
+ .si { color: #e6db74; } /* Literal.String.Interpol */
+ .sx { color: #e6db74; } /* Literal.String.Other */
+ .sr { color: #e6db74; } /* Literal.String.Regex */
+ .s1 { color: #e6db74; } /* Literal.String.Single */
+ .ss { color: #e6db74; } /* Literal.String.Symbol */
+ .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */
+ .vc { color: #f8f8f2; } /* Name.Variable.Class */
+ .vg { color: #f8f8f2; } /* Name.Variable.Global */
+ .vi { color: #f8f8f2; } /* Name.Variable.Instance */
+ .il { color: #ae81ff; } /* Literal.Number.Integer.Long */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index b11499c71ee..c3c7773b9e2 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -72,72 +72,72 @@
green #859900 operators, other keywords
*/
- .c { color: #586e75 } /* Comment */
- .err { color: #93a1a1 } /* Error */
- .g { color: #93a1a1 } /* Generic */
- .k { color: #859900 } /* Keyword */
- .l { color: #93a1a1 } /* Literal */
- .n { color: #93a1a1 } /* Name */
- .o { color: #859900 } /* Operator */
- .x { color: #cb4b16 } /* Other */
- .p { color: #93a1a1 } /* Punctuation */
- .cm { color: #586e75 } /* Comment.Multiline */
- .cp { color: #859900 } /* Comment.Preproc */
- .c1 { color: #586e75 } /* Comment.Single */
- .cs { color: #859900 } /* Comment.Special */
- .gd { color: #2aa198 } /* Generic.Deleted */
- .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
- .gr { color: #dc322f } /* Generic.Error */
- .gh { color: #cb4b16 } /* Generic.Heading */
- .gi { color: #859900 } /* Generic.Inserted */
- .go { color: #93a1a1 } /* Generic.Output */
- .gp { color: #93a1a1 } /* Generic.Prompt */
- .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
- .gu { color: #cb4b16 } /* Generic.Subheading */
- .gt { color: #93a1a1 } /* Generic.Traceback */
- .kc { color: #cb4b16 } /* Keyword.Constant */
- .kd { color: #268bd2 } /* Keyword.Declaration */
- .kn { color: #859900 } /* Keyword.Namespace */
- .kp { color: #859900 } /* Keyword.Pseudo */
- .kr { color: #268bd2 } /* Keyword.Reserved */
- .kt { color: #dc322f } /* Keyword.Type */
- .ld { color: #93a1a1 } /* Literal.Date */
- .m { color: #2aa198 } /* Literal.Number */
- .s { color: #2aa198 } /* Literal.String */
- .na { color: #93a1a1 } /* Name.Attribute */
- .nb { color: #b58900 } /* Name.Builtin */
- .nc { color: #268bd2 } /* Name.Class */
- .no { color: #cb4b16 } /* Name.Constant */
- .nd { color: #268bd2 } /* Name.Decorator */
- .ni { color: #cb4b16 } /* Name.Entity */
- .ne { color: #cb4b16 } /* Name.Exception */
- .nf { color: #268bd2 } /* Name.Function */
- .nl { color: #93a1a1 } /* Name.Label */
- .nn { color: #93a1a1 } /* Name.Namespace */
- .nx { color: #93a1a1 } /* Name.Other */
- .py { color: #93a1a1 } /* Name.Property */
- .nt { color: #268bd2 } /* Name.Tag */
- .nv { color: #268bd2 } /* Name.Variable */
- .ow { color: #859900 } /* Operator.Word */
- .w { color: #93a1a1 } /* Text.Whitespace */
- .mf { color: #2aa198 } /* Literal.Number.Float */
- .mh { color: #2aa198 } /* Literal.Number.Hex */
- .mi { color: #2aa198 } /* Literal.Number.Integer */
- .mo { color: #2aa198 } /* Literal.Number.Oct */
- .sb { color: #586e75 } /* Literal.String.Backtick */
- .sc { color: #2aa198 } /* Literal.String.Char */
- .sd { color: #93a1a1 } /* Literal.String.Doc */
- .s2 { color: #2aa198 } /* Literal.String.Double */
- .se { color: #cb4b16 } /* Literal.String.Escape */
- .sh { color: #93a1a1 } /* Literal.String.Heredoc */
- .si { color: #2aa198 } /* Literal.String.Interpol */
- .sx { color: #2aa198 } /* Literal.String.Other */
- .sr { color: #dc322f } /* Literal.String.Regex */
- .s1 { color: #2aa198 } /* Literal.String.Single */
- .ss { color: #2aa198 } /* Literal.String.Symbol */
- .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
- .vc { color: #268bd2 } /* Name.Variable.Class */
- .vg { color: #268bd2 } /* Name.Variable.Global */
- .vi { color: #268bd2 } /* Name.Variable.Instance */
- .il { color: #2aa198 } /* Literal.Number.Integer.Long */
+ .c { color: #586e75; } /* Comment */
+ .err { color: #93a1a1; } /* Error */
+ .g { color: #93a1a1; } /* Generic */
+ .k { color: #859900; } /* Keyword */
+ .l { color: #93a1a1; } /* Literal */
+ .n { color: #93a1a1; } /* Name */
+ .o { color: #859900; } /* Operator */
+ .x { color: #cb4b16; } /* Other */
+ .p { color: #93a1a1; } /* Punctuation */
+ .cm { color: #586e75; } /* Comment.Multiline */
+ .cp { color: #859900; } /* Comment.Preproc */
+ .c1 { color: #586e75; } /* Comment.Single */
+ .cs { color: #859900; } /* Comment.Special */
+ .gd { color: #2aa198; } /* Generic.Deleted */
+ .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */
+ .gr { color: #dc322f; } /* Generic.Error */
+ .gh { color: #cb4b16; } /* Generic.Heading */
+ .gi { color: #859900; } /* Generic.Inserted */
+ .go { color: #93a1a1; } /* Generic.Output */
+ .gp { color: #93a1a1; } /* Generic.Prompt */
+ .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */
+ .gu { color: #cb4b16; } /* Generic.Subheading */
+ .gt { color: #93a1a1; } /* Generic.Traceback */
+ .kc { color: #cb4b16; } /* Keyword.Constant */
+ .kd { color: #268bd2; } /* Keyword.Declaration */
+ .kn { color: #859900; } /* Keyword.Namespace */
+ .kp { color: #859900; } /* Keyword.Pseudo */
+ .kr { color: #268bd2; } /* Keyword.Reserved */
+ .kt { color: #dc322f; } /* Keyword.Type */
+ .ld { color: #93a1a1; } /* Literal.Date */
+ .m { color: #2aa198; } /* Literal.Number */
+ .s { color: #2aa198; } /* Literal.String */
+ .na { color: #93a1a1; } /* Name.Attribute */
+ .nb { color: #b58900; } /* Name.Builtin */
+ .nc { color: #268bd2; } /* Name.Class */
+ .no { color: #cb4b16; } /* Name.Constant */
+ .nd { color: #268bd2; } /* Name.Decorator */
+ .ni { color: #cb4b16; } /* Name.Entity */
+ .ne { color: #cb4b16; } /* Name.Exception */
+ .nf { color: #268bd2; } /* Name.Function */
+ .nl { color: #93a1a1; } /* Name.Label */
+ .nn { color: #93a1a1; } /* Name.Namespace */
+ .nx { color: #93a1a1; } /* Name.Other */
+ .py { color: #93a1a1; } /* Name.Property */
+ .nt { color: #268bd2; } /* Name.Tag */
+ .nv { color: #268bd2; } /* Name.Variable */
+ .ow { color: #859900; } /* Operator.Word */
+ .w { color: #93a1a1; } /* Text.Whitespace */
+ .mf { color: #2aa198; } /* Literal.Number.Float */
+ .mh { color: #2aa198; } /* Literal.Number.Hex */
+ .mi { color: #2aa198; } /* Literal.Number.Integer */
+ .mo { color: #2aa198; } /* Literal.Number.Oct */
+ .sb { color: #586e75; } /* Literal.String.Backtick */
+ .sc { color: #2aa198; } /* Literal.String.Char */
+ .sd { color: #93a1a1; } /* Literal.String.Doc */
+ .s2 { color: #2aa198; } /* Literal.String.Double */
+ .se { color: #cb4b16; } /* Literal.String.Escape */
+ .sh { color: #93a1a1; } /* Literal.String.Heredoc */
+ .si { color: #2aa198; } /* Literal.String.Interpol */
+ .sx { color: #2aa198; } /* Literal.String.Other */
+ .sr { color: #dc322f; } /* Literal.String.Regex */
+ .s1 { color: #2aa198; } /* Literal.String.Single */
+ .ss { color: #2aa198; } /* Literal.String.Symbol */
+ .bp { color: #268bd2; } /* Name.Builtin.Pseudo */
+ .vc { color: #268bd2; } /* Name.Variable.Class */
+ .vg { color: #268bd2; } /* Name.Variable.Global */
+ .vi { color: #268bd2; } /* Name.Variable.Instance */
+ .il { color: #2aa198; } /* Literal.Number.Integer.Long */
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 657bb5e3cd9..5956a28cafe 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -78,72 +78,72 @@
green #859900 operators, other keywords
*/
- .c { color: #93a1a1 } /* Comment */
- .err { color: #586e75 } /* Error */
- .g { color: #586e75 } /* Generic */
- .k { color: #859900 } /* Keyword */
- .l { color: #586e75 } /* Literal */
- .n { color: #586e75 } /* Name */
- .o { color: #859900 } /* Operator */
- .x { color: #cb4b16 } /* Other */
- .p { color: #586e75 } /* Punctuation */
- .cm { color: #93a1a1 } /* Comment.Multiline */
- .cp { color: #859900 } /* Comment.Preproc */
- .c1 { color: #93a1a1 } /* Comment.Single */
- .cs { color: #859900 } /* Comment.Special */
- .gd { color: #2aa198 } /* Generic.Deleted */
- .ge { color: #586e75; font-style: italic } /* Generic.Emph */
- .gr { color: #dc322f } /* Generic.Error */
- .gh { color: #cb4b16 } /* Generic.Heading */
- .gi { color: #859900 } /* Generic.Inserted */
- .go { color: #586e75 } /* Generic.Output */
- .gp { color: #586e75 } /* Generic.Prompt */
- .gs { color: #586e75; font-weight: bold } /* Generic.Strong */
- .gu { color: #cb4b16 } /* Generic.Subheading */
- .gt { color: #586e75 } /* Generic.Traceback */
- .kc { color: #cb4b16 } /* Keyword.Constant */
- .kd { color: #268bd2 } /* Keyword.Declaration */
- .kn { color: #859900 } /* Keyword.Namespace */
- .kp { color: #859900 } /* Keyword.Pseudo */
- .kr { color: #268bd2 } /* Keyword.Reserved */
- .kt { color: #dc322f } /* Keyword.Type */
- .ld { color: #586e75 } /* Literal.Date */
- .m { color: #2aa198 } /* Literal.Number */
- .s { color: #2aa198 } /* Literal.String */
- .na { color: #586e75 } /* Name.Attribute */
- .nb { color: #b58900 } /* Name.Builtin */
- .nc { color: #268bd2 } /* Name.Class */
- .no { color: #cb4b16 } /* Name.Constant */
- .nd { color: #268bd2 } /* Name.Decorator */
- .ni { color: #cb4b16 } /* Name.Entity */
- .ne { color: #cb4b16 } /* Name.Exception */
- .nf { color: #268bd2 } /* Name.Function */
- .nl { color: #586e75 } /* Name.Label */
- .nn { color: #586e75 } /* Name.Namespace */
- .nx { color: #586e75 } /* Name.Other */
- .py { color: #586e75 } /* Name.Property */
- .nt { color: #268bd2 } /* Name.Tag */
- .nv { color: #268bd2 } /* Name.Variable */
- .ow { color: #859900 } /* Operator.Word */
- .w { color: #586e75 } /* Text.Whitespace */
- .mf { color: #2aa198 } /* Literal.Number.Float */
- .mh { color: #2aa198 } /* Literal.Number.Hex */
- .mi { color: #2aa198 } /* Literal.Number.Integer */
- .mo { color: #2aa198 } /* Literal.Number.Oct */
- .sb { color: #93a1a1 } /* Literal.String.Backtick */
- .sc { color: #2aa198 } /* Literal.String.Char */
- .sd { color: #586e75 } /* Literal.String.Doc */
- .s2 { color: #2aa198 } /* Literal.String.Double */
- .se { color: #cb4b16 } /* Literal.String.Escape */
- .sh { color: #586e75 } /* Literal.String.Heredoc */
- .si { color: #2aa198 } /* Literal.String.Interpol */
- .sx { color: #2aa198 } /* Literal.String.Other */
- .sr { color: #dc322f } /* Literal.String.Regex */
- .s1 { color: #2aa198 } /* Literal.String.Single */
- .ss { color: #2aa198 } /* Literal.String.Symbol */
- .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
- .vc { color: #268bd2 } /* Name.Variable.Class */
- .vg { color: #268bd2 } /* Name.Variable.Global */
- .vi { color: #268bd2 } /* Name.Variable.Instance */
- .il { color: #2aa198 } /* Literal.Number.Integer.Long */
+ .c { color: #93a1a1; } /* Comment */
+ .err { color: #586e75; } /* Error */
+ .g { color: #586e75; } /* Generic */
+ .k { color: #859900; } /* Keyword */
+ .l { color: #586e75; } /* Literal */
+ .n { color: #586e75; } /* Name */
+ .o { color: #859900; } /* Operator */
+ .x { color: #cb4b16; } /* Other */
+ .p { color: #586e75; } /* Punctuation */
+ .cm { color: #93a1a1; } /* Comment.Multiline */
+ .cp { color: #859900; } /* Comment.Preproc */
+ .c1 { color: #93a1a1; } /* Comment.Single */
+ .cs { color: #859900; } /* Comment.Special */
+ .gd { color: #2aa198; } /* Generic.Deleted */
+ .ge { color: #586e75; font-style: italic; } /* Generic.Emph */
+ .gr { color: #dc322f; } /* Generic.Error */
+ .gh { color: #cb4b16; } /* Generic.Heading */
+ .gi { color: #859900; } /* Generic.Inserted */
+ .go { color: #586e75; } /* Generic.Output */
+ .gp { color: #586e75; } /* Generic.Prompt */
+ .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */
+ .gu { color: #cb4b16; } /* Generic.Subheading */
+ .gt { color: #586e75; } /* Generic.Traceback */
+ .kc { color: #cb4b16; } /* Keyword.Constant */
+ .kd { color: #268bd2; } /* Keyword.Declaration */
+ .kn { color: #859900; } /* Keyword.Namespace */
+ .kp { color: #859900; } /* Keyword.Pseudo */
+ .kr { color: #268bd2; } /* Keyword.Reserved */
+ .kt { color: #dc322f; } /* Keyword.Type */
+ .ld { color: #586e75; } /* Literal.Date */
+ .m { color: #2aa198; } /* Literal.Number */
+ .s { color: #2aa198; } /* Literal.String */
+ .na { color: #586e75; } /* Name.Attribute */
+ .nb { color: #b58900; } /* Name.Builtin */
+ .nc { color: #268bd2; } /* Name.Class */
+ .no { color: #cb4b16; } /* Name.Constant */
+ .nd { color: #268bd2; } /* Name.Decorator */
+ .ni { color: #cb4b16; } /* Name.Entity */
+ .ne { color: #cb4b16; } /* Name.Exception */
+ .nf { color: #268bd2; } /* Name.Function */
+ .nl { color: #586e75; } /* Name.Label */
+ .nn { color: #586e75; } /* Name.Namespace */
+ .nx { color: #586e75; } /* Name.Other */
+ .py { color: #586e75; } /* Name.Property */
+ .nt { color: #268bd2; } /* Name.Tag */
+ .nv { color: #268bd2; } /* Name.Variable */
+ .ow { color: #859900; } /* Operator.Word */
+ .w { color: #586e75; } /* Text.Whitespace */
+ .mf { color: #2aa198; } /* Literal.Number.Float */
+ .mh { color: #2aa198; } /* Literal.Number.Hex */
+ .mi { color: #2aa198; } /* Literal.Number.Integer */
+ .mo { color: #2aa198; } /* Literal.Number.Oct */
+ .sb { color: #93a1a1; } /* Literal.String.Backtick */
+ .sc { color: #2aa198; } /* Literal.String.Char */
+ .sd { color: #586e75; } /* Literal.String.Doc */
+ .s2 { color: #2aa198; } /* Literal.String.Double */
+ .se { color: #cb4b16; } /* Literal.String.Escape */
+ .sh { color: #586e75; } /* Literal.String.Heredoc */
+ .si { color: #2aa198; } /* Literal.String.Interpol */
+ .sx { color: #2aa198; } /* Literal.String.Other */
+ .sr { color: #dc322f; } /* Literal.String.Regex */
+ .s1 { color: #2aa198; } /* Literal.String.Single */
+ .ss { color: #2aa198; } /* Literal.String.Symbol */
+ .bp { color: #268bd2; } /* Name.Builtin.Pseudo */
+ .vc { color: #268bd2; } /* Name.Variable.Class */
+ .vg { color: #268bd2; } /* Name.Variable.Global */
+ .vi { color: #268bd2; } /* Name.Variable.Instance */
+ .il { color: #2aa198; } /* Literal.Number.Integer.Long */
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 36a80a916b2..6f31a5235c0 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -86,7 +86,7 @@
background-color: #fafe3d !important;
}
- .hll { background-color: #f8f8f8 }
+ .hll { background-color: #f8f8f8; }
.c { color: #998; font-style: italic; }
.err { color: #a61717; background-color: #e3d2d2; }
.k { font-weight: bold; }
diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss
index 5bfe9bcb443..8d1a6020ca4 100644
--- a/app/assets/stylesheets/mailers/repository_push_email.scss
+++ b/app/assets/stylesheets/mailers/repository_push_email.scss
@@ -78,7 +78,7 @@ span.highlight_word {
background-color: #fafe3d !important;
}
-.hll { background-color: #f8f8f8 }
+.hll { background-color: #f8f8f8; }
.c { color: #998; font-style: italic; }
.err { color: #a61717; background-color: #e3d2d2; }
.k { font-weight: bold; }
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index fc12964872d..ced8c4a9907 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -2,22 +2,28 @@ img {
max-width: 100%;
height: auto;
}
+
p.details {
font-style: italic;
- color: #777
+ color: #777;
}
+
.footer > p {
font-size: small;
- color: #777
+ color: #777;
}
+
pre.commit-message {
white-space: pre-wrap;
}
+
.file-stats > a {
text-decoration: none;
+
> .new-file {
color: #090;
}
+
> .deleted-file {
color: #b00;
}
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 8f71381f5c4..140d589024b 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -22,7 +22,7 @@
.admin-filter form {
.select2-container {
- width: 100%
+ width: 100%;
}
.controls {
@@ -31,7 +31,7 @@
.form-actions {
padding-left: 130px;
- background: #fff
+ background: #fff;
}
.visibility-levels {
@@ -106,26 +106,33 @@
.table {
table-layout: fixed;
}
+
.subheading {
padding-bottom: $gl-padding;
}
+
.message {
word-wrap: break-word;
}
+
.btn {
white-space: normal;
padding: $gl-btn-padding;
}
+
th {
width: 15%;
+
&.wide {
width: 55%;
}
}
+
@media (max-width: $screen-sm-max) {
th {
width: 100%;
}
+
td {
width: 100%;
float: left;
@@ -137,6 +144,7 @@
margin-left: $btn-side-margin;
margin-top: 3px;
}
+
span {
font-size: 19px;
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index ecc5b24e360..6e81c12aa55 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -162,6 +162,10 @@ lex
list-style: none;
overflow-y: scroll;
overflow-x: hidden;
+
+ &.is-smaller {
+ height: calc(100% - 185px);
+ }
}
.board-list-loading {
@@ -233,3 +237,31 @@ lex
margin-right: 5px;
}
}
+
+.board-new-issue-form {
+ margin: 5px;
+}
+
+.board-issue-count-holder {
+ margin-top: -3px;
+
+ .btn {
+ line-height: 12px;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+}
+
+.board-issue-count {
+ padding-right: 10px;
+ padding-left: 10px;
+ line-height: 21px;
+ border-radius: $border-radius-base;
+ border: 1px solid $border-color;
+
+ &.has-btn {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-width: 1px 0 1px 1px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 194a39a8377..2fbf0cf34bf 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -137,6 +137,7 @@
.retry-link {
color: $gl-link-color;
+
&:hover {
text-decoration: underline;
}
@@ -218,6 +219,7 @@
.build-detail-row {
margin-bottom: 5px;
+
&:last-of-type {
margin-bottom: 0;
}
@@ -233,3 +235,9 @@
right: 0;
margin-top: -17px;
}
+
+@media (min-width: $screen-md-min) {
+ .sub-nav.build {
+ width: calc(100% + #{$gutter_width});
+ }
+}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 53ec0002afe..264e7e01a34 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -51,6 +51,7 @@
margin-left: 4px;
}
}
+
.commit-committer-link,
.commit-author-link {
color: $gl-gray;
@@ -108,21 +109,25 @@
line-height: 20px;
}
}
+
.new-file {
a {
color: $gl-text-green;
}
}
+
.renamed-file {
a {
color: $gl-text-orange;
}
}
+
.deleted-file {
a {
color: $gl-text-red;
}
}
+
.edit-file {
a {
color: $gl-text-color;
@@ -158,6 +163,7 @@
position: absolute;
z-index: 1;
}
+
> textarea {
background-color: rgba(0, 0, 0, 0.0);
font-family: inherit;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index dc57a837155..2b5621e20d6 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -161,6 +161,7 @@
.branch-commit {
color: $gl-gray;
+
.commit-id, .commit-row-message {
color: $gl-gray;
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 778471a34d7..d732008de3d 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -50,7 +50,7 @@
.bordered-box {
border: 1px solid $border-color;
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
}
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index 42928ee279c..76225ed8d06 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -5,6 +5,7 @@
background: $background-color;
border-top-left-radius: 0;
}
+
border-top-left-radius: 0;
}
}
@@ -17,6 +18,7 @@
float: left;
@extend .col-md-2;
}
+
.btn {
margin-left: 5px;
float: left;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b8ef76cc74e..bdc82a8f0f5 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -33,6 +33,19 @@
font-size: smaller;
}
}
+
+ .file-title {
+ cursor: pointer;
+
+ &:hover {
+ background-color: $dark-background-color;
+ }
+
+ .diff-toggle-caret {
+ padding-right: 6px;
+ }
+ }
+
.diff-content {
overflow: auto;
overflow-y: hidden;
@@ -123,15 +136,18 @@
max-width: 50px;
width: 35px;
@include user-select(none);
+
a {
float: left;
width: 35px;
font-weight: normal;
+
&:hover {
text-decoration: underline;
}
}
}
+
.line_content {
display: block;
margin: 0;
@@ -151,10 +167,12 @@
white-space: pre-wrap;
}
}
+
.image {
background: #ddd;
text-align: center;
padding: 30px;
+
.wrap {
display: inline-block;
}
@@ -163,6 +181,7 @@
display: inline-block;
background-color: #fff;
line-height: 0;
+
img {
border: 1px solid #fff;
background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%),
@@ -171,6 +190,7 @@
background-position: 0 0, 5px 5px;
max-width: 100%;
}
+
&.deleted {
border: 1px solid $deleted;
}
@@ -179,6 +199,7 @@
border: 1px solid $added;
}
}
+
.image-info {
font-size: 12px;
margin: 5px 0 0;
@@ -193,6 +214,7 @@
margin: auto;
position: relative;
}
+
.swipe-wrap {
overflow: hidden;
border-left: 1px solid #999;
@@ -201,10 +223,12 @@
top: 13px;
right: 7px;
}
+
.frame {
top: 0;
right: 0;
position: absolute;
+
&.deleted {
margin: 0;
display: block;
@@ -212,6 +236,7 @@
right: 7px;
}
}
+
.swipe-bar {
display: block;
height: 100%;
@@ -219,14 +244,17 @@
z-index: 100;
position: absolute;
cursor: pointer;
+
&:hover {
.top-handle {
background-position: -15px 3px;
}
+
.bottom-handle {
background-position: -15px -11px;
}
}
+
.top-handle {
display: block;
height: 14px;
@@ -235,6 +263,7 @@
top: 0;
background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
}
+
.bottom-handle {
display: block;
height: 14px;
@@ -252,12 +281,14 @@
margin: auto;
position: relative;
}
+
.frame.added, .frame.deleted {
position: absolute;
display: block;
top: 0;
left: 0;
}
+
.controls {
display: block;
height: 14px;
@@ -311,6 +342,7 @@
}
//.view.onion-skin
}
+
.view-modes {
padding: 10px;
text-align: center;
@@ -328,19 +360,24 @@
border-left: 1px solid #c1c1c1;
padding: 0 12px 0 16px;
cursor: pointer;
+
&:first-child {
border-left: none;
}
+
&:hover {
text-decoration: underline;
}
+
&.active {
&:hover {
text-decoration: none;
}
+
cursor: default;
color: #333;
}
+
&.disabled {
display: none;
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index e1304335271..029dabd2138 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,7 +1,7 @@
.file-editor {
#editor {
border: none;
- @include border-radius(0);
+ border-radius: 0;
height: 500px;
margin: 0;
padding: 0;
@@ -15,6 +15,7 @@
.cancel-btn {
color: #b94a48;
+
&:hover {
color: #b94a48;
}
@@ -70,16 +71,20 @@
.soft-wrap-toggle {
margin: 0 $btn-side-margin;
+
.soft-wrap {
display: block;
}
+
.no-wrap {
display: none;
}
+
&.soft-wrap-active {
.soft-wrap {
display: none;
}
+
.no-wrap {
display: block;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 2e14bdb0eb3..9d3492abfb5 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -1,4 +1,15 @@
+.environments-container,
+.deployments-container {
+ width: 100%;
+ overflow: auto;
+}
+
.environments {
+ .deployment-column {
+ .avatar {
+ float: none;
+ }
+ }
.commit-title {
margin: 0;
@@ -9,6 +20,7 @@
width: 12px;
}
+ .external-url,
.dropdown-new {
color: $table-text-gray;
}
@@ -21,6 +33,7 @@
}
}
+ .build-link,
.branch-name {
color: $gl-dark-link-color;
}
@@ -32,13 +45,31 @@
font-size: 14px;
}
}
+
+ .deployment {
+ .build-column {
+
+ .build-link {
+ color: $gl-dark-link-color;
+ }
+
+ .avatar {
+ float: none;
+ }
+ }
+ }
}
-.table.builds.environments {
- min-width: 500px;
+.table.ci-table.environments {
.icon-container {
width: 20px;
text-align: center;
}
+
+ .branch-commit {
+ .commit-id {
+ margin-right: 0;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 1d00da1266c..5d9a76dac05 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -78,6 +78,7 @@
margin-bottom: 0;
}
}
+
.event-note-icon {
color: #777;
float: left;
@@ -86,21 +87,23 @@
margin-right: 5px;
}
}
+
.event_icon {
position: relative;
float: right;
border: 1px solid #eee;
padding: 5px;
- @include border-radius(5px);
+ border-radius: 5px;
background: $gray-light;
margin-left: 10px;
top: -6px;
+
img {
width: 20px;
}
}
- &:last-child { border: none }
+ &:last-child { border: none; }
.event_commits {
li {
@@ -109,6 +112,7 @@
padding: 3px;
padding-left: 0;
border: none;
+
.commit-row-title {
font-size: $gl-font-size;
}
@@ -117,6 +121,7 @@
&.commits-stat {
display: block;
padding: 0 3px 0 0;
+
&:hover {
background: none;
}
@@ -158,6 +163,7 @@
overflow: visible;
max-width: 100%;
}
+
.avatar {
display: none;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 185ce970e71..ee2a398f031 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,17 +1,3 @@
-.member-search-form {
- float: left;
-
- input[type='search'] {
- width: 225px;
- vertical-align: bottom;
-
- @media (max-width: $screen-xs-max) {
- width: 100px;
- vertical-align: bottom;
- }
- }
-}
-
.milestone-row {
@include str-truncated(90%);
}
@@ -48,6 +34,7 @@
.group-right-buttons {
position: absolute;
right: 16px;
+
.btn {
@include btn-gray;
padding: 3px 10px;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 00ab42bec5c..a48b4c65db8 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -23,28 +23,28 @@
color: #555;
tbody:first-child tr:first-child {
- padding-top: 0
+ padding-top: 0;
}
th {
padding-top: 15px;
line-height: 1.5;
color: #333;
- text-align: left
+ text-align: left;
}
td {
padding-top: 3px;
padding-bottom: 3px;
vertical-align: top;
- line-height: 20px
+ line-height: 20px;
}
.shortcut {
padding-right: 10px;
color: #999;
text-align: right;
- white-space: nowrap
+ white-space: nowrap;
}
.key {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 41079b6eeb5..230b927a17d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -27,6 +27,7 @@
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
+
.color-label {
padding: 6px 10px;
}
@@ -128,7 +129,7 @@
}
.selectbox {
- display: none
+ display: none;
}
.btn-clipboard {
@@ -199,7 +200,7 @@
display: none;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
- display: block
+ display: block;
}
width: $sidebar_collapsed_width;
@@ -276,7 +277,7 @@
}
&.btn-primary {
- @extend .btn-primary
+ @extend .btn-primary;
}
}
@@ -400,6 +401,7 @@
.js-issuable-selector {
width: 100%;
}
+
@media (max-width: $screen-sm-max) {
margin-bottom: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 3ac34cbc829..623da67a239 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -37,6 +37,7 @@ ul.related-merge-requests > li {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
+
.merge-request-id {
flex-shrink: 0;
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 38c7cd98e41..9bac6d46355 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -1,7 +1,8 @@
.suggest-colors {
margin-top: 5px;
+
a {
- @include border-radius(4px);
+ border-radius: 4px;
width: 30px;
height: 30px;
display: inline-block;
@@ -17,7 +18,7 @@
overflow: hidden;
a {
- @include border-radius(0);
+ border-radius: 0;
width: (100% / 7);
margin-right: 0;
margin-bottom: -5px;
@@ -59,6 +60,13 @@
width: 200px;
margin-bottom: 0;
}
+
+ .label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ max-width: 100%;
+ }
}
.label-description {
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 6926448519e..8290519dc25 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -3,6 +3,7 @@
font-size: 19px;
color: red;
}
+
.correct-syntax {
font-size: 19px;
color: #47a447;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 403171d4532..4c0c0839fe2 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -17,6 +17,7 @@
line-height: 1.5;
p {
+ font-size: 18px;
color: #888;
}
@@ -36,10 +37,14 @@
}
}
- .login-box {
- background: #fafafa;
- border-radius: 10px;
- box-shadow: 0 0 2px #ccc;
+ p {
+ font-size: 13px;
+ }
+
+ .login-box, .omniauth-container {
+ box-shadow: 0 0 0 1px $border-color;
+ border-bottom-right-radius: 2px;
+ border-bottom-left-radius: 2px;
padding: 15px;
.login-heading h3 {
@@ -58,42 +63,127 @@
a.forgot {
float: right;
- padding-top: 6px
+ padding-top: 6px;
}
.nav .active a {
background: transparent;
}
- }
- .form-control {
- font-size: 14px;
- padding: 10px 8px;
- width: 100%;
- height: auto;
-
- &.top {
- @include border-radius(5px 5px 0 0);
- margin-bottom: 0;
+ // Styles the glowing border of focused input for username async validation
+ .login-body {
+ font-size: 13px;
+
+
+ input + p {
+ margin-top: 5px;
+ }
+
+ .gl-field-success-outline {
+ border: 1px solid $green-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
+ border: 0 none;
+ }
+ }
+
+ .gl-field-error-outline {
+ border: 1px solid $red-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
+ border: 0 none;
+ }
+ }
+
+ .username .validation-success,
+ .gl-field-success-message {
+ color: $green-normal;
+ }
+
+ .username .validation-error,
+ .gl-field-error-message {
+ color: $red-normal;
+ }
+
+ .gl-field-hint {
+ color: $gl-text-color;
+ }
+
}
+ }
- &.bottom {
- @include border-radius(0 0 5px 5px);
- border-top: 0;
- margin-bottom: 20px;
+ .omniauth-container {
+ p {
+ margin: 0;
}
+ }
+
+ .new-session-tabs {
+ display: -webkit-flex;
+ display: flex;
+ box-shadow: 0 0 0 1px $border-color;
+ border-top-right-radius: 2px;
+ border-top-left-radius: 2px;
+
+ li {
+ flex: 1;
+ text-align: center;
+
+ &:last-of-type {
+ border-left: 1px solid $border-color;
+ }
+
+ &:not(.active) {
+ background-color: $gray-light;
+ }
+
+ a {
+ width: 100%;
+ font-size: 18px;
- &.middle {
- border-top: 0;
- margin-bottom: 0;
- @include border-radius(0);
+ &:hover {
+ border: 1px solid transparent;
+ }
+ }
+
+ &.active {
+ border-bottom: 1px solid $border-color;
+
+ a {
+ border: none;
+ border-bottom: 2px solid $link-underline-blue;
+ color: $black;
+
+ &:hover {
+ border-bottom: 2px solid $link-underline-blue;
+ }
+ }
+ }
}
+ }
+
+ .form-control {
&:active, &:focus {
background-color: #fff;
}
}
+ label {
+ font-weight: normal;
+ }
+
+ .submit-container {
+ margin-top: 16px;
+ }
+
+ input[type="submit"] {
+ @extend .btn-block;
+ margin-bottom: 0;
+ }
+
.devise-errors {
h2 {
margin-top: 0;
@@ -101,14 +191,6 @@
color: #a00;
}
}
-
- .remember-me {
- margin-top: -10px;
-
- label {
- font-weight: normal;
- }
- }
}
@media (max-width: $screen-xs-max) {
@@ -127,3 +209,35 @@
height: 32px;
}
}
+
+.devise-layout-html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+// Fixes footer container to bottom of viewport
+.devise-layout-html body {
+ // offset height of fixed header + 1 to avoid scroll
+ height: calc(100% - 51px);
+ margin: 0;
+ padding: 0;
+
+ .page-wrap {
+ min-height: 100%;
+ position: relative;
+ }
+
+ .footer-container, hr.footer-fixed {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 40px;
+ background: $white-light;
+ }
+
+ .navless-container {
+ padding: 65px; // height of footer + bottom padding of email confirmation link
+ }
+}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
new file mode 100644
index 00000000000..756efa9c7fa
--- /dev/null
+++ b/app/assets/stylesheets/pages/members.scss
@@ -0,0 +1,98 @@
+.project-members-title {
+ padding-bottom: 10px;
+ border-bottom: 1px solid $border-color;
+}
+
+.member {
+ .list-item-name {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ width: 50%;
+ }
+
+ strong {
+ font-weight: 600;
+ }
+ }
+
+ .controls {
+ @media (min-width: $screen-sm-min) {
+ display: -webkit-flex;
+ display: flex;
+ width: 400px;
+ max-width: 50%;
+ }
+ }
+
+ .form-horizontal {
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ display: -webkit-flex;
+ display: flex;
+ width: 100%;
+ margin-top: 3px;
+ }
+ }
+
+ .btn-remove {
+ width: 100%;
+
+ @media (min-width: $screen-sm-min) {
+ width: auto;
+ }
+ }
+}
+
+.member-form-control {
+ @media (max-width: $screen-xs-max) {
+ padding: 5px 0;
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ @media (min-width: $screen-sm-min) {
+ width: 50%;
+ }
+}
+
+.member-access-text {
+ margin-left: auto;
+ line-height: 43px;
+}
+
+.member.existing-title {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ }
+}
+
+.member-search-form {
+ position: relative;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ }
+
+ .form-control {
+ width: 100%;
+ padding-right: 35px;
+
+ @media (min-width: $screen-sm-min) {
+ width: 350px;
+ }
+ }
+}
+
+.member-search-btn {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 35px;
+ padding-left: 10px;
+ padding-right: 10px;
+ color: $gray-darkest;
+ background: transparent;
+ border: 0;
+ outline: 0;
+}
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 5ec660799e3..49013d7cac9 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -131,6 +131,7 @@ $colors: (
}
}
}
+
&.head {
background-color: map-get($colors, #{$color}_header_head_neutral);
border-color: map-get($colors, #{$color}_header_head_neutral);
@@ -174,6 +175,7 @@ $colors: (
background-color: map-get($colors, #{$color}_line_not_chosen);
}
}
+
&.head {
background-color: map-get($colors, #{$color}_line_head_neutral);
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f2c2e06ad3e..f14f13494e0 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -6,10 +6,11 @@
background: $background-color;
color: $gl-gray;
border: 1px solid $border-color;
- @include border-radius(2px);
+ border-radius: 2px;
form {
margin-bottom: 0;
+
.clearfix {
margin-bottom: 0;
}
@@ -46,6 +47,7 @@
&.right {
float: right;
+
a {
color: $gl-gray;
}
@@ -121,6 +123,10 @@
color: #5c5d5e;
}
+ .js-deployment-link {
+ display: inline-block;
+ }
+
.mr-widget-body {
h4 {
font-weight: 600;
@@ -197,6 +203,7 @@
padding-top: 2px;
padding-bottom: 2px;
list-style: none;
+
&:hover {
background: none;
}
@@ -213,6 +220,19 @@
word-break: break-all;
}
+.commits-empty {
+ text-align: center;
+
+ h4 {
+ padding-top: 20px;
+ padding-bottom: 10px;
+ }
+
+ svg {
+ width: 230px;
+ }
+}
+
.mr-list {
.merge-request {
padding: 10px 15px;
@@ -273,12 +293,6 @@
line-height: 31px;
}
-.builds {
- .table-holder {
- overflow-x: auto;
- }
-}
-
.panel-new-merge-request {
.panel-heading {
padding: 5px 10px;
@@ -366,7 +380,7 @@
}
.table-holder {
- .builds {
+ .ci-table {
th {
background-color: $white-light;
@@ -398,8 +412,12 @@
padding: 16px;
}
+ .content-block {
+ border-top: 1px solid $border-color;
+ padding: $gl-padding-top $gl-padding;
+ }
+
.comments-disabled-notif {
- padding: 10px 16px;
.btn {
margin-left: 5px;
}
@@ -410,10 +428,6 @@
margin: 0 7px;
}
- .comments-disabled-notif {
- border-top: 1px solid $border-color;
- }
-
.dropdown-title {
color: $gl-text-color;
}
@@ -430,3 +444,12 @@
margin-bottom: 20px;
}
}
+
+.merge-request-tabs {
+ background-color: #fff;
+
+ &.affix {
+ top: 100px;
+ z-index: 9;
+ }
+}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 8c2ba3ed58c..dd6d1783667 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -59,6 +59,7 @@
color: $gl-placeholder-color;
margin-right: 5px;
}
+
.avatar {
float: none;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index bd875b9823f..17f28959414 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -11,6 +11,7 @@
filter: alpha(opacity=100);
}
}
+
.diff-file,
.discussion {
.new-note {
@@ -194,6 +195,7 @@
min-height: 140px;
max-height: 500px;
}
+
.note-form-actions {
background: transparent;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 54124a3d658..fffcdc812a7 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -147,9 +147,18 @@ ul.notes {
// Diff code in discussion view
.discussion-body .diff-file {
+ .file-title {
+ cursor: default;
+
+ &:hover {
+ background-color: $gray-light;
+ }
+ }
+
.diff-header > span {
margin-right: 10px;
}
+
.line_content {
white-space: pre-wrap;
}
@@ -334,7 +343,7 @@ ul.notes {
.add-diff-note {
margin-top: -4px;
- @include border-radius(40px);
+ border-radius: 40px;
background: #fff;
padding: 4px;
font-size: 16px;
@@ -345,6 +354,7 @@ ul.notes {
width: 32px;
// "hide" it by default
display: none;
+
&:hover {
background: $gl-info;
color: #fff;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 68fc6da6c1b..7b71876b822 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -20,7 +20,7 @@
margin: 4px;
}
- .table.builds {
+ .table.ci-table {
min-width: 1200px;
.branch-commit {
@@ -44,13 +44,20 @@
overflow: auto;
}
-.table.builds {
+.table.ci-table {
min-width: 900px;
&.pipeline {
min-width: 650px;
}
+ &.builds-page {
+
+ tr {
+ height: 71px;
+ }
+ }
+
tr {
th {
padding: 16px 8px;
@@ -176,7 +183,7 @@
&::after {
content: '';
width: 8px;
- position: absolute;;
+ position: absolute;
right: -7px;
bottom: 8px;
border-bottom: 2px solid $border-color;
@@ -229,9 +236,12 @@
.fa {
color: $table-text-gray;
- margin-right: 6px;
font-size: 14px;
}
+
+ svg, .fa {
+ margin-right: 0;
+ }
}
.btn-remove {
@@ -272,18 +282,8 @@
.toggle-pipeline-btn {
background-color: $gray-dark;
- .caret {
- border-top: none;
- border-bottom: 4px solid;
- }
-
&.graph-collapsed {
background-color: $white-light;
-
- .caret {
- border-bottom: none;
- border-top: 4px solid;
- }
}
}
@@ -310,16 +310,41 @@
.stage-column {
display: inline-block;
vertical-align: top;
- margin-right: 65px;
+
+ &:not(:last-child) {
+ margin-right: 44px;
+ }
+
+ &.left-margin {
+ &:not(:first-child) {
+ margin-left: 44px;
+
+ .left-connector {
+ &::before {
+ content: '';
+ position: absolute;
+ top: 48%;
+ left: -48px;
+ border-top: 2px solid $border-color;
+ width: 48px;
+ height: 1px;
+ }
+ }
+ }
+ }
+
+ &.no-margin {
+ margin: 0;
+ }
li {
list-style: none;
}
.stage-name {
- margin-bottom: 15px;
+ margin: 0 0 15px 10px;
font-weight: bold;
- width: 150px;
+ width: 176px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -328,17 +353,24 @@
.build {
border: 1px solid $border-color;
position: relative;
- padding: 6px 10px;
+ padding: 7px 10px 8px;
border-radius: 30px;
- width: 150px;
+ width: 186px;
margin-bottom: 10px;
+ &:hover {
+ background-color: $gray-lighter;
+
+ .dropdown-menu-toggle {
+ background-color: transparent;
+ }
+ }
+
&.playable {
- background-color: $gray-light;
svg {
- height: 12px;
- width: 12px;
+ height: 13px;
+ width: 20px;
position: relative;
top: 1px;
@@ -349,10 +381,20 @@
}
.build-content {
- width: 130px;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ width: 164px;
+
+ .ci-status-icon {
+ svg {
+ height: 20px;
+ width: 20px;
+ }
+ }
.ci-status-text {
- width: 110px;
+ width: 135px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -363,44 +405,56 @@
}
a {
- color: $layout-link-gray;
+ color: $gl-text-color-light;
text-decoration: none;
-
- &:hover {
- .ci-status-text {
- text-decoration: underline;
- }
- }
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
- color: $layout-link-gray;
+ color: $gl-text-color-light;
+ flex-grow: 1;
.ci-status-text {
- width: 80px;
+ max-width: 112px;
+ width: auto;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
- width: 200px;
+ width: 186px;
left: auto;
- right: -214px;
+ right: -197px;
top: -9px;
- max-height: 245px;
- overflow-y: scroll;
- a:hover {
- .ci-status-text {
- text-decoration: none;
+ ul {
+ max-height: 245px;
+ overflow: auto;
+ }
+
+ a {
+ color: $gl-text-color;
+ padding: 7px 8px 8px;
+
+ &:hover {
+ background-color: $blue-light-transparent;
+ border-radius: 3px;
+
+ .ci-status-text {
+ text-decoration: none;
+ }
}
}
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+
.ci-status-text {
- width: 145px;
+ width: 112px;
}
.arrow {
@@ -433,9 +487,10 @@
}
.badge {
- background-color: $gray-dark;
- color: $layout-link-gray;
+ background-color: $gray-darker;
+ color: $gl-text-color-light;
font-weight: normal;
+ margin-left: $btn-xs-side-margin;
}
}
@@ -449,10 +504,10 @@
&::after {
content: '';
position: absolute;
- top: 50%;
- right: -69px;
+ top: 48%;
+ right: -48px;
border-top: 2px solid $border-color;
- width: 69px;
+ width: 48px;
height: 1px;
}
}
@@ -461,25 +516,25 @@
&:not(:first-child) {
&::after, &::before {
content: '';
- top: -47px;
+ top: -49px;
position: absolute;
border-bottom: 2px solid $border-color;
- width: 20px;
- height: 65px;
+ width: 25px;
+ height: 69px;
}
// Right connecting curves
&::after {
- right: -20px;
+ right: -25px;
border-right: 2px solid $border-color;
- border-radius: 0 0 15px;
+ border-radius: 0 0 20px;
}
// Left connecting curves
&::before {
- left: -20px;
+ left: -25px;
border-left: 2px solid $border-color;
- border-radius: 0 0 0 15px;
+ border-radius: 0 0 0 20px;
}
}
@@ -487,8 +542,9 @@
&:nth-child(2) {
&::after, &::before {
height: 29px;
- top: -10px;
+ top: -9px;
}
+
.curve {
display: block;
}
@@ -545,20 +601,20 @@
width: 21px;
height: 25px;
position: absolute;
- top: -29px;
+ top: -32px;
border-top: 2px solid $border-color;
}
&::after {
- left: -39px;
+ left: -44px;
border-right: 2px solid $border-color;
- border-radius: 0 15px;
+ border-radius: 0 20px;
}
&::before {
- right: -39px;
+ right: -44px;
border-left: 2px solid $border-color;
- border-radius: 15px 0 0;
+ border-radius: 20px 0 0;
}
}
}
@@ -574,19 +630,31 @@
}
}
-.pipelines.tab-pane {
+.tab-pane {
- .content-list.pipelines {
- overflow: auto;
- }
+ &.pipelines {
- .stage {
- max-width: 100px;
- width: 100px;
+ .content-list.pipelines {
+ overflow: auto;
+ }
+
+ .stage {
+ max-width: 100px;
+ width: 100px;
+ }
+
+ .pipeline-actions {
+ min-width: initial;
+ }
}
- .pipeline-actions {
- min-width: initial;
+ &.builds {
+
+ .ci-table {
+ tr {
+ height: 71px;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 0fcdaf94a21..ed80d2beec2 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -94,7 +94,7 @@
.profile-user-bio {
// Limits the width of the user bio for readability.
max-width: 600px;
- margin: 15px auto 0;
+ margin: 10px auto;
padding: 0 16px;
}
@@ -213,29 +213,22 @@
}
.user-profile {
+
.cover-controls a {
margin-left: 5px;
}
+
.profile-header {
margin: 0 auto;
+
.avatar-holder {
width: 90px;
- display: inline-block;
- }
- .user-info {
- display: inline-block;
- text-align: left;
- vertical-align: middle;
- margin-left: 15px;
- .handle {
- color: $gl-gray-light;
- }
- .member-date {
- margin-bottom: 4px;
- }
+ margin: 0 auto 10px;
}
}
+
@media (max-width: $screen-xs-max) {
+
.cover-block {
padding-top: 20px;
}
@@ -250,6 +243,7 @@
.btn {
-webkit-flex-grow: 1;
flex-grow: 1;
+
&:first-child {
margin-left: 0;
}
@@ -258,10 +252,6 @@
}
}
-.user-profile-nav {
- margin-top: 15px;
-}
-
table.u2f-registrations {
th:not(:last-child), td:not(:last-child) {
border-right: solid 1px transparent;
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index e5859fe7384..f8da0983b77 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -4,7 +4,7 @@
text-align: center;
.preview {
- @include border-radius(4px);
+ border-radius: 4px;
height: 80px;
margin-bottom: 10px;
@@ -47,7 +47,7 @@
width: 160px;
img {
- @include border-radius(4px);
+ border-radius: 4px;
max-width: 100%;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 87548dcb590..d30f02340b9 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -17,34 +17,43 @@
&.features .control-label {
font-weight: normal;
}
+
.form-group {
margin-bottom: 5px;
}
+
&> .form-group {
padding-left: 0;
}
}
+
.help-block {
margin-bottom: 10px;
}
+
.project-path {
padding-right: 0;
+
.form-control {
border-radius: $border-radius-base;
}
}
+
.input-group > div {
&:last-child {
padding-right: 0;
}
}
+
@media (max-width: $screen-xs-max) {
.input-group > div {
margin-bottom: 14px;
+
&:last-child {
margin-bottom: 0;
}
}
+
fieldset > .form-group:first-child {
padding-right: 0;
}
@@ -56,6 +65,7 @@
border-radius: 3px;
border: 1px solid #e5e5e5;
}
+
&+ .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@@ -201,6 +211,7 @@
pointer-events: none;
}
}
+
.count {
@include btn-gray;
display: inline-block;
@@ -354,35 +365,40 @@ a.deploy-project-label {
justify-content: flex-start;
.fork-thumbnail {
- @include border-radius($border-radius-base);
+ border-radius: $border-radius-base;
background-color: $white-light;
border: 1px solid $border-white-light;
height: 202px;
margin: $gl-padding;
text-align: center;
width: 169px;
+
&:hover, &.forked {
background-color: $row-hover;
border-color: $row-hover-border;
}
+
.no-avatar {
width: 100px;
height: 100px;
background-color: $gray-light;
border: 1px solid $gray-dark;
margin: 0 auto;
- @include border-radius(50%);
+ border-radius: 50%;
+
i {
font-size: 100px;
color: $gray-dark;
}
}
+
a {
display: block;
width: 100%;
height: 100%;
padding-top: $gl-padding;
color: $gl-gray;
+
.caption {
min-height: 30px;
padding: $gl-padding 0;
@@ -390,7 +406,7 @@ a.deploy-project-label {
}
img {
- @include border-radius(50%);
+ border-radius: 50%;
max-width: 100px;
}
}
@@ -496,7 +512,7 @@ pre.light-well {
}
.light-well {
- @include border-radius (2px);
+ border-radius: 2px;
color: #5b6169;
font-size: 13px;
@@ -644,6 +660,7 @@ pre.light-well {
.clone-options {
display: table-cell;
+
a.btn {
width: 100%;
}
@@ -832,6 +849,7 @@ pre.light-well {
.form-control {
min-width: 100px;
}
+
.select2-choice {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index eec22c5dc96..7b3878c91df 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -6,6 +6,7 @@
&.runner-state-shared {
background: #32b186;
}
+
&.runner-state-specific {
background: #3498db;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 0ee7ceecae5..f1d53c7b8bc 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -4,7 +4,7 @@
margin-right: 10px;
border: 1px solid #eee;
white-space: nowrap;
- @include border-radius(4px);
+ border-radius: 4px;
&:hover {
text-decoration: none;
@@ -65,6 +65,7 @@
.ci-status-icon-success {
color: $gl-success;
}
+
.ci-status-icon-failed {
color: $gl-danger;
}
@@ -77,6 +78,7 @@
.ci-status-icon-running {
color: $blue-normal;
}
+
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 41ad10f07bd..99c0f6362d0 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -5,6 +5,7 @@
.file-finder {
width: 50%;
+
.file-finder-input {
width: 95%;
display: inline-block;
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index c9846103762..3fa7fa3d7e3 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -23,15 +23,19 @@
.term-bold {
font-weight: bold;
}
+
.term-italic {
font-style: italic;
}
+
.term-conceal {
visibility: hidden;
}
+
.term-underline {
text-decoration: underline;
}
+
.term-cross {
text-decoration: line-through;
}
@@ -39,48 +43,63 @@
.term-fg-black {
color: $black;
}
+
.term-fg-red {
color: $red;
}
+
.term-fg-green {
color: $green;
}
+
.term-fg-yellow {
color: $yellow;
}
+
.term-fg-blue {
color: $blue;
}
+
.term-fg-magenta {
color: $magenta;
}
+
.term-fg-cyan {
color: $cyan;
}
+
.term-fg-white {
color: $white;
}
+
.term-fg-l-black {
color: $l-black;
}
+
.term-fg-l-red {
color: $l-red;
}
+
.term-fg-l-green {
color: $l-green;
}
+
.term-fg-l-yellow {
color: $l-yellow;
}
+
.term-fg-l-blue {
color: $l-blue;
}
+
.term-fg-l-magenta {
color: $l-magenta;
}
+
.term-fg-l-cyan {
color: $l-cyan;
}
+
.term-fg-l-white {
color: $l-white;
}
@@ -88,818 +107,1087 @@
.term-bg-black {
background-color: $black;
}
+
.term-bg-red {
background-color: $red;
}
+
.term-bg-green {
background-color: $green;
}
+
.term-bg-yellow {
background-color: $yellow;
}
+
.term-bg-blue {
background-color: $blue;
}
+
.term-bg-magenta {
background-color: $magenta;
}
+
.term-bg-cyan {
background-color: $cyan;
}
+
.term-bg-white {
background-color: $white;
}
+
.term-bg-l-black {
background-color: $l-black;
}
+
.term-bg-l-red {
background-color: $l-red;
}
+
.term-bg-l-green {
background-color: $l-green;
}
+
.term-bg-l-yellow {
background-color: $l-yellow;
}
+
.term-bg-l-blue {
background-color: $l-blue;
}
+
.term-bg-l-magenta {
background-color: $l-magenta;
}
+
.term-bg-l-cyan {
background-color: $l-cyan;
}
+
.term-bg-l-white {
background-color: $l-white;
}
-
.xterm-fg-0 {
color: #000;
}
+
.xterm-fg-1 {
color: #800000;
}
+
.xterm-fg-2 {
color: #008000;
}
+
.xterm-fg-3 {
color: #808000;
}
+
.xterm-fg-4 {
color: #000080;
}
+
.xterm-fg-5 {
color: #800080;
}
+
.xterm-fg-6 {
color: #008080;
}
+
.xterm-fg-7 {
color: #c0c0c0;
}
+
.xterm-fg-8 {
color: #808080;
}
+
.xterm-fg-9 {
color: #f00;
}
+
.xterm-fg-10 {
color: #0f0;
}
+
.xterm-fg-11 {
color: #ff0;
}
+
.xterm-fg-12 {
color: #00f;
}
+
.xterm-fg-13 {
color: #f0f;
}
+
.xterm-fg-14 {
color: #0ff;
}
+
.xterm-fg-15 {
color: #fff;
}
+
.xterm-fg-16 {
color: #000;
}
+
.xterm-fg-17 {
color: #00005f;
}
+
.xterm-fg-18 {
color: #000087;
}
+
.xterm-fg-19 {
color: #0000af;
}
+
.xterm-fg-20 {
color: #0000d7;
}
+
.xterm-fg-21 {
color: #00f;
}
+
.xterm-fg-22 {
color: #005f00;
}
+
.xterm-fg-23 {
color: #005f5f;
}
+
.xterm-fg-24 {
color: #005f87;
}
+
.xterm-fg-25 {
color: #005faf;
}
+
.xterm-fg-26 {
color: #005fd7;
}
+
.xterm-fg-27 {
color: #005fff;
}
+
.xterm-fg-28 {
color: #008700;
}
+
.xterm-fg-29 {
color: #00875f;
}
+
.xterm-fg-30 {
color: #008787;
}
+
.xterm-fg-31 {
color: #0087af;
}
+
.xterm-fg-32 {
color: #0087d7;
}
+
.xterm-fg-33 {
color: #0087ff;
}
+
.xterm-fg-34 {
color: #00af00;
}
+
.xterm-fg-35 {
color: #00af5f;
}
+
.xterm-fg-36 {
color: #00af87;
}
+
.xterm-fg-37 {
color: #00afaf;
}
+
.xterm-fg-38 {
color: #00afd7;
}
+
.xterm-fg-39 {
color: #00afff;
}
+
.xterm-fg-40 {
color: #00d700;
}
+
.xterm-fg-41 {
color: #00d75f;
}
+
.xterm-fg-42 {
color: #00d787;
}
+
.xterm-fg-43 {
color: #00d7af;
}
+
.xterm-fg-44 {
color: #00d7d7;
}
+
.xterm-fg-45 {
color: #00d7ff;
}
+
.xterm-fg-46 {
color: #0f0;
}
+
.xterm-fg-47 {
color: #00ff5f;
}
+
.xterm-fg-48 {
color: #00ff87;
}
+
.xterm-fg-49 {
color: #00ffaf;
}
+
.xterm-fg-50 {
color: #00ffd7;
}
+
.xterm-fg-51 {
color: #0ff;
}
+
.xterm-fg-52 {
color: #5f0000;
}
+
.xterm-fg-53 {
color: #5f005f;
}
+
.xterm-fg-54 {
color: #5f0087;
}
+
.xterm-fg-55 {
color: #5f00af;
}
+
.xterm-fg-56 {
color: #5f00d7;
}
+
.xterm-fg-57 {
color: #5f00ff;
}
+
.xterm-fg-58 {
color: #5f5f00;
}
+
.xterm-fg-59 {
color: #5f5f5f;
}
+
.xterm-fg-60 {
color: #5f5f87;
}
+
.xterm-fg-61 {
color: #5f5faf;
}
+
.xterm-fg-62 {
color: #5f5fd7;
}
+
.xterm-fg-63 {
color: #5f5fff;
}
+
.xterm-fg-64 {
color: #5f8700;
}
+
.xterm-fg-65 {
color: #5f875f;
}
+
.xterm-fg-66 {
color: #5f8787;
}
+
.xterm-fg-67 {
color: #5f87af;
}
+
.xterm-fg-68 {
color: #5f87d7;
}
+
.xterm-fg-69 {
color: #5f87ff;
}
+
.xterm-fg-70 {
color: #5faf00;
}
+
.xterm-fg-71 {
color: #5faf5f;
}
+
.xterm-fg-72 {
color: #5faf87;
}
+
.xterm-fg-73 {
color: #5fafaf;
}
+
.xterm-fg-74 {
color: #5fafd7;
}
+
.xterm-fg-75 {
color: #5fafff;
}
+
.xterm-fg-76 {
color: #5fd700;
}
+
.xterm-fg-77 {
color: #5fd75f;
}
+
.xterm-fg-78 {
color: #5fd787;
}
+
.xterm-fg-79 {
color: #5fd7af;
}
+
.xterm-fg-80 {
color: #5fd7d7;
}
+
.xterm-fg-81 {
color: #5fd7ff;
}
+
.xterm-fg-82 {
color: #5fff00;
}
+
.xterm-fg-83 {
color: #5fff5f;
}
+
.xterm-fg-84 {
color: #5fff87;
}
+
.xterm-fg-85 {
color: #5fffaf;
}
+
.xterm-fg-86 {
color: #5fffd7;
}
+
.xterm-fg-87 {
color: #5fffff;
}
+
.xterm-fg-88 {
color: #870000;
}
+
.xterm-fg-89 {
color: #87005f;
}
+
.xterm-fg-90 {
color: #870087;
}
+
.xterm-fg-91 {
color: #8700af;
}
+
.xterm-fg-92 {
color: #8700d7;
}
+
.xterm-fg-93 {
color: #8700ff;
}
+
.xterm-fg-94 {
color: #875f00;
}
+
.xterm-fg-95 {
color: #875f5f;
}
+
.xterm-fg-96 {
color: #875f87;
}
+
.xterm-fg-97 {
color: #875faf;
}
+
.xterm-fg-98 {
color: #875fd7;
}
+
.xterm-fg-99 {
color: #875fff;
}
+
.xterm-fg-100 {
color: #878700;
}
+
.xterm-fg-101 {
color: #87875f;
}
+
.xterm-fg-102 {
color: #878787;
}
+
.xterm-fg-103 {
color: #8787af;
}
+
.xterm-fg-104 {
color: #8787d7;
}
+
.xterm-fg-105 {
color: #8787ff;
}
+
.xterm-fg-106 {
color: #87af00;
}
+
.xterm-fg-107 {
color: #87af5f;
}
+
.xterm-fg-108 {
color: #87af87;
}
+
.xterm-fg-109 {
color: #87afaf;
}
+
.xterm-fg-110 {
color: #87afd7;
}
+
.xterm-fg-111 {
color: #87afff;
}
+
.xterm-fg-112 {
color: #87d700;
}
+
.xterm-fg-113 {
color: #87d75f;
}
+
.xterm-fg-114 {
color: #87d787;
}
+
.xterm-fg-115 {
color: #87d7af;
}
+
.xterm-fg-116 {
color: #87d7d7;
}
+
.xterm-fg-117 {
color: #87d7ff;
}
+
.xterm-fg-118 {
color: #87ff00;
}
+
.xterm-fg-119 {
color: #87ff5f;
}
+
.xterm-fg-120 {
color: #87ff87;
}
+
.xterm-fg-121 {
color: #87ffaf;
}
+
.xterm-fg-122 {
color: #87ffd7;
}
+
.xterm-fg-123 {
color: #87ffff;
}
+
.xterm-fg-124 {
color: #af0000;
}
+
.xterm-fg-125 {
color: #af005f;
}
+
.xterm-fg-126 {
color: #af0087;
}
+
.xterm-fg-127 {
color: #af00af;
}
+
.xterm-fg-128 {
color: #af00d7;
}
+
.xterm-fg-129 {
color: #af00ff;
}
+
.xterm-fg-130 {
color: #af5f00;
}
+
.xterm-fg-131 {
color: #af5f5f;
}
+
.xterm-fg-132 {
color: #af5f87;
}
+
.xterm-fg-133 {
color: #af5faf;
}
+
.xterm-fg-134 {
color: #af5fd7;
}
+
.xterm-fg-135 {
color: #af5fff;
}
+
.xterm-fg-136 {
color: #af8700;
}
+
.xterm-fg-137 {
color: #af875f;
}
+
.xterm-fg-138 {
color: #af8787;
}
+
.xterm-fg-139 {
color: #af87af;
}
+
.xterm-fg-140 {
color: #af87d7;
}
+
.xterm-fg-141 {
color: #af87ff;
}
+
.xterm-fg-142 {
color: #afaf00;
}
+
.xterm-fg-143 {
color: #afaf5f;
}
+
.xterm-fg-144 {
color: #afaf87;
}
+
.xterm-fg-145 {
color: #afafaf;
}
+
.xterm-fg-146 {
color: #afafd7;
}
+
.xterm-fg-147 {
color: #afafff;
}
+
.xterm-fg-148 {
color: #afd700;
}
+
.xterm-fg-149 {
color: #afd75f;
}
+
.xterm-fg-150 {
color: #afd787;
}
+
.xterm-fg-151 {
color: #afd7af;
}
+
.xterm-fg-152 {
color: #afd7d7;
}
+
.xterm-fg-153 {
color: #afd7ff;
}
+
.xterm-fg-154 {
color: #afff00;
}
+
.xterm-fg-155 {
color: #afff5f;
}
+
.xterm-fg-156 {
color: #afff87;
}
+
.xterm-fg-157 {
color: #afffaf;
}
+
.xterm-fg-158 {
color: #afffd7;
}
+
.xterm-fg-159 {
color: #afffff;
}
+
.xterm-fg-160 {
color: #d70000;
}
+
.xterm-fg-161 {
color: #d7005f;
}
+
.xterm-fg-162 {
color: #d70087;
}
+
.xterm-fg-163 {
color: #d700af;
}
+
.xterm-fg-164 {
color: #d700d7;
}
+
.xterm-fg-165 {
color: #d700ff;
}
+
.xterm-fg-166 {
color: #d75f00;
}
+
.xterm-fg-167 {
color: #d75f5f;
}
+
.xterm-fg-168 {
color: #d75f87;
}
+
.xterm-fg-169 {
color: #d75faf;
}
+
.xterm-fg-170 {
color: #d75fd7;
}
+
.xterm-fg-171 {
color: #d75fff;
}
+
.xterm-fg-172 {
color: #d78700;
}
+
.xterm-fg-173 {
color: #d7875f;
}
+
.xterm-fg-174 {
color: #d78787;
}
+
.xterm-fg-175 {
color: #d787af;
}
+
.xterm-fg-176 {
color: #d787d7;
}
+
.xterm-fg-177 {
color: #d787ff;
}
+
.xterm-fg-178 {
color: #d7af00;
}
+
.xterm-fg-179 {
color: #d7af5f;
}
+
.xterm-fg-180 {
color: #d7af87;
}
+
.xterm-fg-181 {
color: #d7afaf;
}
+
.xterm-fg-182 {
color: #d7afd7;
}
+
.xterm-fg-183 {
color: #d7afff;
}
+
.xterm-fg-184 {
color: #d7d700;
}
+
.xterm-fg-185 {
color: #d7d75f;
}
+
.xterm-fg-186 {
color: #d7d787;
}
+
.xterm-fg-187 {
color: #d7d7af;
}
+
.xterm-fg-188 {
color: #d7d7d7;
}
+
.xterm-fg-189 {
color: #d7d7ff;
}
+
.xterm-fg-190 {
color: #d7ff00;
}
+
.xterm-fg-191 {
color: #d7ff5f;
}
+
.xterm-fg-192 {
color: #d7ff87;
}
+
.xterm-fg-193 {
color: #d7ffaf;
}
+
.xterm-fg-194 {
color: #d7ffd7;
}
+
.xterm-fg-195 {
color: #d7ffff;
}
+
.xterm-fg-196 {
color: #f00;
}
+
.xterm-fg-197 {
color: #ff005f;
}
+
.xterm-fg-198 {
color: #ff0087;
}
+
.xterm-fg-199 {
color: #ff00af;
}
+
.xterm-fg-200 {
color: #ff00d7;
}
+
.xterm-fg-201 {
color: #f0f;
}
+
.xterm-fg-202 {
color: #ff5f00;
}
+
.xterm-fg-203 {
color: #ff5f5f;
}
+
.xterm-fg-204 {
color: #ff5f87;
}
+
.xterm-fg-205 {
color: #ff5faf;
}
+
.xterm-fg-206 {
color: #ff5fd7;
}
+
.xterm-fg-207 {
color: #ff5fff;
}
+
.xterm-fg-208 {
color: #ff8700;
}
+
.xterm-fg-209 {
color: #ff875f;
}
+
.xterm-fg-210 {
color: #ff8787;
}
+
.xterm-fg-211 {
color: #ff87af;
}
+
.xterm-fg-212 {
color: #ff87d7;
}
+
.xterm-fg-213 {
color: #ff87ff;
}
+
.xterm-fg-214 {
color: #ffaf00;
}
+
.xterm-fg-215 {
color: #ffaf5f;
}
+
.xterm-fg-216 {
color: #ffaf87;
}
+
.xterm-fg-217 {
color: #ffafaf;
}
+
.xterm-fg-218 {
color: #ffafd7;
}
+
.xterm-fg-219 {
color: #ffafff;
}
+
.xterm-fg-220 {
color: #ffd700;
}
+
.xterm-fg-221 {
color: #ffd75f;
}
+
.xterm-fg-222 {
color: #ffd787;
}
+
.xterm-fg-223 {
color: #ffd7af;
}
+
.xterm-fg-224 {
color: #ffd7d7;
}
+
.xterm-fg-225 {
color: #ffd7ff;
}
+
.xterm-fg-226 {
color: #ff0;
}
+
.xterm-fg-227 {
color: #ffff5f;
}
+
.xterm-fg-228 {
color: #ffff87;
}
+
.xterm-fg-229 {
color: #ffffaf;
}
+
.xterm-fg-230 {
color: #ffffd7;
}
+
.xterm-fg-231 {
color: #fff;
}
+
.xterm-fg-232 {
color: #080808;
}
+
.xterm-fg-233 {
color: #121212;
}
+
.xterm-fg-234 {
color: #1c1c1c;
}
+
.xterm-fg-235 {
color: #262626;
}
+
.xterm-fg-236 {
color: #303030;
}
+
.xterm-fg-237 {
color: #3a3a3a;
}
+
.xterm-fg-238 {
color: #444;
}
+
.xterm-fg-239 {
color: #4e4e4e;
}
+
.xterm-fg-240 {
color: #585858;
}
+
.xterm-fg-241 {
color: #626262;
}
+
.xterm-fg-242 {
color: #6c6c6c;
}
+
.xterm-fg-243 {
color: #767676;
}
+
.xterm-fg-244 {
color: #808080;
}
+
.xterm-fg-245 {
color: #8a8a8a;
}
+
.xterm-fg-246 {
color: #949494;
}
+
.xterm-fg-247 {
color: #9e9e9e;
}
+
.xterm-fg-248 {
color: #a8a8a8;
}
+
.xterm-fg-249 {
color: #b2b2b2;
}
+
.xterm-fg-250 {
color: #bcbcbc;
}
+
.xterm-fg-251 {
color: #c6c6c6;
}
+
.xterm-fg-252 {
color: #d0d0d0;
}
+
.xterm-fg-253 {
color: #dadada;
}
+
.xterm-fg-254 {
color: #e4e4e4;
}
+
.xterm-fg-255 {
color: #eee;
}
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 82055006ac0..762e36ee2e9 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -37,7 +37,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end
def preview
- @message = broadcast_message_params[:message]
+ @broadcast_message = BroadcastMessage.new(broadcast_message_params)
end
protected
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bd4ba384b29..705824502eb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -45,6 +45,10 @@ class ApplicationController < ActionController::Base
redirect_to request.referer.present? ? :back : default, options
end
+ def not_found
+ render_404
+ end
+
protected
# This filter handles both private tokens and personal access tokens
@@ -173,7 +177,8 @@ class ApplicationController < ActionController::Base
end
def event_filter
- filters = cookies['event_filter'].split(',') if cookies['event_filter'].present?
+ # Split using comma to maintain backward compatibility Ex/ "filter1,filter2"
+ filters = cookies['event_filter'].split(',')[0] if cookies['event_filter'].present?
@event_filter ||= EventFilter.new(filters)
end
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
deleted file mode 100644
index 5bb7d499cdc..00000000000
--- a/app/controllers/ci/application_controller.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
- class ApplicationController < ::ApplicationController
- def self.railtie_helpers_paths
- "app/helpers/ci"
- end
- end
-end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 78012960252..3eb485de9db 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class LintsController < ApplicationController
+ class LintsController < ::ApplicationController
before_action :authenticate_user!
def show
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index aa894fde36b..ff297d6ff13 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class ProjectsController < Ci::ApplicationController
+ class ProjectsController < ::ApplicationController
before_action :project
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 38e5943eb76..a62c6211372 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def trending
- @projects = TrendingProjectsFinder.new.execute
- @projects = filter_projects(@projects)
+ @projects = filter_projects(Project.trending)
@projects = @projects.page(params[:page])
respond_to do |format|
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
deleted file mode 100644
index 83eec1bf4a2..00000000000
--- a/app/controllers/namespaces_controller.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class NamespacesController < ApplicationController
- skip_before_action :authenticate_user!
-
- def show
- namespace = Namespace.find_by(path: params[:id])
-
- if namespace
- if namespace.is_a?(Group)
- group = namespace
- else
- user = namespace.owner
- end
- end
-
- if user
- redirect_to user_path(user)
- elsif group && can?(current_user, :read_group, group)
- redirect_to group_path(group)
- elsif current_user.nil?
- authenticate_user!
- else
- render_404
- end
- end
-end
diff --git a/app/controllers/projects/board_lists_controller.rb b/app/controllers/projects/board_lists_controller.rb
deleted file mode 100644
index 3cfb08d5822..00000000000
--- a/app/controllers/projects/board_lists_controller.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-class Projects::BoardListsController < Projects::ApplicationController
- respond_to :json
-
- before_action :authorize_admin_list!
-
- rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
-
- def create
- list = Boards::Lists::CreateService.new(project, current_user, list_params).execute
-
- if list.valid?
- render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
- else
- render json: list.errors, status: :unprocessable_entity
- end
- end
-
- def update
- service = Boards::Lists::MoveService.new(project, current_user, move_params)
-
- if service.execute
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def destroy
- service = Boards::Lists::DestroyService.new(project, current_user, params)
-
- if service.execute
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def generate
- service = Boards::Lists::GenerateService.new(project, current_user)
-
- if service.execute
- render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
- else
- head :unprocessable_entity
- end
- end
-
- private
-
- def authorize_admin_list!
- return render_403 unless can?(current_user, :admin_list, project)
- end
-
- def list_params
- params.require(:list).permit(:label_id)
- end
-
- def move_params
- params.require(:list).permit(:position).merge(id: params[:id])
- end
-
- def record_not_found(exception)
- render json: { error: exception.message }, status: :not_found
- end
-end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 4aa7982eab4..71eb56aed0b 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -2,6 +2,7 @@ module Projects
module Boards
class IssuesController < Boards::ApplicationController
before_action :authorize_read_issue!, only: [:index]
+ before_action :authorize_create_issue!, only: [:create]
before_action :authorize_update_issue!, only: [:update]
def index
@@ -9,16 +10,22 @@ module Projects
issues = issues.page(params[:page])
render json: {
- issues: issues.as_json(
- only: [:iid, :title, :confidential],
- include: {
- assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
- labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
- }),
+ issues: serialize_as_json(issues),
size: issues.total_count
}
end
+ def create
+ service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
+ issue = service.execute
+
+ if issue.valid?
+ render json: serialize_as_json(issue)
+ else
+ render json: issue.errors, status: :unprocessable_entity
+ end
+ end
+
def update
service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
@@ -43,16 +50,33 @@ module Projects
return render_403 unless can?(current_user, :read_issue, project)
end
+ def authorize_create_issue!
+ return render_403 unless can?(current_user, :admin_issue, project)
+ end
+
def authorize_update_issue!
return render_403 unless can?(current_user, :update_issue, issue)
end
def filter_params
- params.merge(id: params[:list_id])
+ params.merge(board_id: params[:board_id], id: params[:list_id])
end
def move_params
- params.permit(:id, :from_list_id, :to_list_id)
+ params.permit(:board_id, :id, :from_list_id, :to_list_id)
+ end
+
+ def issue_params
+ params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
+ end
+
+ def serialize_as_json(resource)
+ resource.as_json(
+ only: [:iid, :title, :confidential],
+ include: {
+ assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
+ labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
+ })
end
end
end
diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb
index b995f586737..76ae41319c4 100644
--- a/app/controllers/projects/boards/lists_controller.rb
+++ b/app/controllers/projects/boards/lists_controller.rb
@@ -5,11 +5,11 @@ module Projects
before_action :authorize_read_list!, only: [:index]
def index
- render json: serialize_as_json(project.board.lists)
+ render json: serialize_as_json(board.lists)
end
def create
- list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute
+ list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
@@ -19,7 +19,7 @@ module Projects
end
def update
- list = project.board.lists.movable.find(params[:id])
+ list = board.lists.movable.find(params[:id])
service = ::Boards::Lists::MoveService.new(project, current_user, move_params)
if service.execute(list)
@@ -30,8 +30,8 @@ module Projects
end
def destroy
- list = project.board.lists.destroyable.find(params[:id])
- service = ::Boards::Lists::DestroyService.new(project, current_user, params)
+ list = board.lists.destroyable.find(params[:id])
+ service = ::Boards::Lists::DestroyService.new(project, current_user)
if service.execute(list)
head :ok
@@ -43,8 +43,8 @@ module Projects
def generate
service = ::Boards::Lists::GenerateService.new(project, current_user)
- if service.execute
- render json: serialize_as_json(project.board.lists.movable)
+ if service.execute(board)
+ render json: serialize_as_json(board.lists.movable)
else
head :unprocessable_entity
end
@@ -60,6 +60,10 @@ module Projects
return render_403 unless can?(current_user, :read_list, project)
end
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def list_params
params.require(:list).permit(:label_id)
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 0035633b774..808affa4f98 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,12 +1,28 @@
class Projects::BoardsController < Projects::ApplicationController
include IssuableCollections
-
- respond_to :html
- before_action :authorize_read_board!, only: [:show]
+ before_action :authorize_read_board!, only: [:index, :show]
+
+ def index
+ @boards = ::Boards::ListService.new(project, current_user).execute
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: serialize_as_json(@boards)
+ end
+ end
+ end
def show
- ::Boards::CreateService.new(project, current_user).execute
+ @board = project.boards.find(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: serialize_as_json(@board)
+ end
+ end
end
private
@@ -14,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController
def authorize_read_board!
return access_denied! unless can?(current_user, :read_board, project)
end
+
+ def serialize_as_json(resource)
+ resource.as_json(only: [:id])
+ end
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 092ef32e6e3..923e7340e69 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -38,12 +38,12 @@ class Projects::GraphsController < Projects::ApplicationController
@languages = @languages.map do |language|
name, share = language
- color = Digest::SHA256.hexdigest(name)[0...6]
+ color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}"
{
value: (share.to_f * 100 / total).round(2),
label: name,
- color: "##{color}",
- highlight: "##{color}"
+ color: color,
+ highlight: color
}
end
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 7a7475a7345..ae060abee5c 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -1,6 +1,7 @@
class Projects::GroupLinksController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_admin_project!
+ before_action :authorize_admin_project_member!, only: [:update]
def index
@group_links = project.project_group_links.all
@@ -27,9 +28,26 @@ class Projects::GroupLinksController < Projects::ApplicationController
redirect_to namespace_project_group_links_path(project.namespace, project)
end
+ def update
+ @group_link = @project.project_group_links.find(params[:id])
+
+ @group_link.update_attributes(group_link_params)
+ end
+
def destroy
project.project_group_links.find(params[:id]).destroy
- redirect_to namespace_project_group_links_path(project.namespace, project)
+ respond_to do |format|
+ format.html do
+ redirect_to namespace_project_group_links_path(project.namespace, project)
+ end
+ format.js { head :ok }
+ end
+ end
+
+ protected
+
+ def group_link_params
+ params.require(:group_link).permit(:group_access, :expires_at)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ef13e0677d2..96041b07647 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -159,7 +159,8 @@ class Projects::IssuesController < Projects::ApplicationController
protected
def issue
- @noteable = @issue ||= @project.issues.find_by(iid: params[:id]) || redirect_old
+ # The Sortable default scope causes performance issues when used with find_by
+ @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8c8c56228ad..9207c954335 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check,
- :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts
+ :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
@@ -19,6 +19,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
+ before_action :apply_diff_view_cookie!, only: [:new_diffs]
+ before_action :build_merge_request, only: [:new, :new_diffs]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -29,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Allow modify merge_request
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
+ before_action :authenticate_user!, only: [:assign_related_issues]
+
before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
def index
@@ -210,29 +214,26 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def new
- apply_diff_view_cookie!
-
- build_merge_request
- @noteable = @merge_request
-
- @target_branches = if @merge_request.target_project
- @merge_request.target_project.repository.branch_names
- else
- []
- end
-
- @target_project = merge_request.target_project
- @source_project = merge_request.source_project
- @commits = @merge_request.compare_commits.reverse
- @commit = @merge_request.diff_head_commit
- @base_commit = @merge_request.diff_base_commit
- @diffs = @merge_request.diffs(diff_options) if @merge_request.compare
- @diff_notes_disabled = true
- @pipeline = @merge_request.pipeline
- @statuses = @pipeline.statuses.relevant if @pipeline
+ define_new_vars
+ end
- @note_counts = Note.where(commit_id: @commits.map(&:id)).
- group(:commit_id).count
+ def new_diffs
+ respond_to do |format|
+ format.html do
+ define_new_vars
+ render "new"
+ end
+ format.json do
+ @diffs = if @merge_request.can_be_created
+ @merge_request.diffs(diff_options)
+ else
+ []
+ end
+ @diff_notes_disabled = true
+
+ render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs) }
+ end
+ end
end
def create
@@ -355,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render layout: false
end
+ def assign_related_issues
+ result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
+
+ respond_to do |format|
+ format.html do
+ case result[:count]
+ when 0
+ flash[:error] = "Failed to assign you issues related to the merge request"
+ when 1
+ flash[:notice] = "1 issue has been assigned to you"
+ else
+ flash[:notice] = "#{result[:count]} issues have been assigned to you"
+ end
+
+ redirect_to(merge_request_path(@merge_request))
+ end
+ end
+ end
+
def ci_status
pipeline = @merge_request.pipeline
if pipeline
@@ -383,6 +403,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render json: response
end
+ def ci_environments_status
+ environments =
+ begin
+ @merge_request.environments.map do |environment|
+ next unless can?(current_user, :read_environment, environment)
+
+ project = environment.project
+ deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
+
+ {
+ id: environment.id,
+ name: environment.name,
+ url: namespace_project_environment_path(project.namespace, project, environment),
+ external_url: environment.external_url,
+ external_url_formatted: environment.formatted_external_url,
+ deployed_at: deployment.try(:created_at),
+ deployed_at_formatted: deployment.try(:formatted_deployment_time)
+ }
+ end.compact
+ end
+
+ render json: environments
+ end
+
protected
def selected_target_project
@@ -490,6 +534,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
)
end
+ def define_new_vars
+ @noteable = @merge_request
+
+ @target_branches = if @merge_request.target_project
+ @merge_request.target_project.repository.branch_names
+ else
+ []
+ end
+
+ @target_project = merge_request.target_project
+ @source_project = merge_request.source_project
+ @commits = @merge_request.compare_commits.reverse
+ @commit = @merge_request.diff_head_commit
+ @base_commit = @merge_request.diff_base_commit
+
+ @pipeline = @merge_request.pipeline
+ @statuses = @pipeline.statuses.relevant if @pipeline
+ @note_counts = Note.where(commit_id: @commits.map(&:id)).
+ group(:commit_id).count
+ end
+
def invalid_mr
# Render special view for MR with removed target branch
render 'invalid'
@@ -521,7 +586,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
- @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
end
def compared_diff_version
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index f56b256984b..37a86ed0523 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -5,34 +5,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
def index
+ @group_links = @project.project_group_links
+
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
if params[:search].present?
users = @project.users.search(params[:search]).to_a
@project_members = @project_members.where(user_id: users)
- end
-
- @project_members = @project_members.order('access_level DESC')
-
- @group = @project.group
-
- if @group
- @group_members = @group.group_members
- @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
-
- if params[:search].present?
- users = @group.users.search(params[:search]).to_a
- @group_members = @group_members.where(user_id: users)
- end
- @group_members = @group_members.order('access_level DESC')
+ @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
+ @project_members = @project_members.order(access_level: :desc).page(params[:page])
+
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
@project_member = @project.project_members.new
- @project_group_links = @project.project_group_links
end
def create
@@ -43,6 +32,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController
current_user: current_user
)
+ if params[:group_ids].present?
+ group_ids = params[:group_ids].split(',')
+ groups = Group.where(id: group_ids)
+
+ groups.each do |group|
+ next unless can?(current_user, :read_group, group)
+
+ project.project_group_links.create(
+ group: group,
+ group_access: params[:access_level],
+ expires_at: params[:expires_at]
+ )
+ end
+ end
+
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 6ea8ee62bc5..8fea20cefef 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController
def show
@tag = @repository.find_tag(params[:id])
+ return render_404 unless @tag
+
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index d198782138a..dee57e4a388 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,10 +1,10 @@
class SnippetsController < ApplicationController
include ToggleAwardEmoji
- before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+ before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
# Allow read snippet
- before_action :authorize_read_snippet!, only: [:show, :raw]
+ before_action :authorize_read_snippet!, only: [:show, :raw, :download]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -12,7 +12,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
- skip_before_action :authenticate_user!, only: [:index, :show, :raw]
+ skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
layout 'snippets'
respond_to :html
@@ -75,6 +75,14 @@ class SnippetsController < ApplicationController
)
end
+ def download
+ send_data(
+ @snippet.content,
+ type: 'text/plain; charset=utf-8',
+ filename: @snippet.sanitized_file_name
+ )
+ end
+
protected
def snippet
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 838ecc837e4..6a881b271d7 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,6 +1,6 @@
class UsersController < ApplicationController
skip_before_action :authenticate_user!
- before_action :user
+ before_action :user, except: [:exists]
before_action :authorize_read_user!, only: [:show]
def show
@@ -85,6 +85,10 @@ class UsersController < ApplicationController
render 'calendar_activities', layout: false
end
+ def exists
+ render json: { exists: Namespace.where(path: params[:username].downcase).any? }
+ end
+
private
def authorize_read_user!
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
deleted file mode 100644
index c1e434d9926..00000000000
--- a/app/finders/trending_projects_finder.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Finder for retrieving public trending projects in a given time range.
-class TrendingProjectsFinder
- # current_user - The currently logged in User, if any.
- # last_months - The number of months to limit the trending data to.
- def execute(months_limit = 1)
- Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do
- Project.public_only.trending(months_limit.months.ago)
- end
- end
-
- private
-
- def cache_key_for(months)
- "trending_projects/#{months}"
- end
-end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index de13e7a1fc2..16136d02530 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -16,7 +16,7 @@ module AppearancesHelper
end
def brand_text
- markdown(brand_item.description)
+ markdown_field(brand_item, :description)
end
def brand_item
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 6de25bea654..6229384817b 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -11,18 +11,6 @@ module ApplicationSettingsHelper
current_application_settings.signin_enabled?
end
- def extra_sign_in_text
- current_application_settings.sign_in_text
- end
-
- def after_sign_up_text
- current_application_settings.after_sign_up_text
- end
-
- def shared_runners_text
- current_application_settings.shared_runners_text
- end
-
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index df41473543b..b7e0ff8ecd0 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -4,15 +4,18 @@ module AvatarsHelper
user: commit_or_event.author,
user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email,
+ css_class: 'hidden-xs'
}))
end
def user_avatar(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
+ css_class = options[:css_class] || ''
+
avatar = image_tag(
avatar_icon(options[:user] || options[:user_email], avatar_size),
- class: "avatar has-tooltip hidden-xs s#{avatar_size}",
+ class: "avatar has-tooltip s#{avatar_size} #{css_class}",
alt: "#{user_name}'s avatar",
title: user_name,
data: { container: 'body' }
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
new file mode 100644
index 00000000000..b7247ffa8b2
--- /dev/null
+++ b/app/helpers/boards_helper.rb
@@ -0,0 +1,12 @@
+module BoardsHelper
+ def board_data
+ board = @board || @boards.first
+
+ {
+ endpoint: namespace_project_boards_path(@project.namespace, @project),
+ board_id: board.id,
+ disabled: !can?(current_user, :admin_list, @project),
+ issue_link_base: namespace_project_issues_path(@project.namespace, @project)
+ }
+ end
+end
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 43a29c96bca..eb03ced67eb 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -3,7 +3,7 @@ module BroadcastMessagesHelper
return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
- icon('bullhorn') << ' ' << render_broadcast_message(message.message)
+ icon('bullhorn') << ' ' << render_broadcast_message(message)
end
end
@@ -32,7 +32,7 @@ module BroadcastMessagesHelper
end
end
- def render_broadcast_message(message)
- Banzai.render(message, pipeline: :broadcast_message).html_safe
+ def render_broadcast_message(broadcast_message)
+ Banzai.render_field(broadcast_message, :message).html_safe
end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index b478580978b..85e1dc33ee8 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -15,13 +15,14 @@ module ButtonHelper
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
+ css_class = data[:class] || 'btn-clipboard btn-transparent'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
- class: "btn btn-clipboard",
+ class: "btn #{css_class}",
data: data,
type: :button,
- title: "Copy to Clipboard"
+ title: 'Copy to Clipboard'
end
def http_clone_button(project, placement = 'right', append_link: true)
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 1a259656f31..0772d848289 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -13,14 +13,12 @@ module GitlabMarkdownHelper
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
- escaped_body = if body.start_with?('<img')
- body
- else
- escape_once(body)
- end
-
- user = current_user if defined?(current_user)
- gfm_body = Banzai.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
+ context = {
+ project: @project,
+ current_user: (current_user if defined?(current_user)),
+ pipeline: :single_line,
+ }
+ gfm_body = Banzai.render(body, context)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -51,17 +49,15 @@ module GitlabMarkdownHelper
context[:project] ||= @project
html = Banzai.render(text, context)
+ banzai_postprocess(html, context)
+ end
- context.merge!(
- current_user: (current_user if defined?(current_user)),
-
- # RelativeLinkFilter
- requested_path: @path,
- project_wiki: @project_wiki,
- ref: @ref
- )
+ def markdown_field(object, field)
+ object = object.for_display if object.respond_to?(:for_display)
+ return "" unless object.present?
- Banzai.post_process(html, context)
+ html = Banzai.render_field(object, field)
+ banzai_postprocess(html, object.banzai_render_context(field))
end
def asciidoc(text)
@@ -196,4 +192,18 @@ module GitlabMarkdownHelper
icon(options[:icon])
end
end
+
+ # Calls Banzai.post_process with some common context options
+ def banzai_postprocess(html, context)
+ context.merge!(
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ requested_path: @path,
+ project_wiki: @project_wiki,
+ ref: @ref
+ )
+
+ Banzai.post_process(html, context)
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 8b212b0327a..1644c346dd8 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -113,14 +113,13 @@ module IssuesHelper
end
end
- def award_user_list(awards, current_user)
+ def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name
end
- # Take first 9 OR current user + first 9
current_user_name = names.delete('You')
- names = names.first(9).insert(0, current_user_name).compact
+ names = names.insert(0, current_user_name).compact.first(limit)
names << "#{awards.size - names.size} more." if awards.size > names.size
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 8abe7865fed..249cb44e9d5 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -72,6 +72,19 @@ module MergeRequestsHelper
)
end
+ def mr_assign_issues_link
+ issues = MergeRequests::AssignIssuesService.new(@project,
+ current_user,
+ merge_request: @merge_request,
+ closes_issues: mr_closes_issues
+ ).assignable_issues
+ path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ if issues.present?
+ pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
+ link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
+ end
+ end
+
def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
@@ -110,4 +123,8 @@ module MergeRequestsHelper
def version_index(merge_request_diff)
@merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
end
+
+ def different_base?(version1, version2)
+ version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8a7446b7cc7..aba3a3f9c5d 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -153,8 +153,18 @@ module SearchHelper
search_path(options)
end
- # Sanitize html generated after parsing markdown from issue description or comment
- def search_md_sanitize(html)
+ # Sanitize a HTML field for search display. Most tags are stripped out and the
+ # maximum length is set to 200 characters.
+ def search_md_sanitize(object, field)
+ html = markdown_field(object, field)
+ html = Truncato.truncate(
+ html,
+ count_tags: false,
+ count_tail: false,
+ max_length: 200
+ )
+
+ # Truncato's filtered_tags and filtered_attributes are not quite the same
sanitize(html, tags: %w(a p ol ul li pre code))
end
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index b01a244032d..2340453831e 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,4 +1,8 @@
class AbuseReport < ActiveRecord::Base
+ include CacheMarkdownField
+
+ cache_markdown_field :message, pipeline: :single_line
+
belongs_to :reporter, class_name: 'User'
belongs_to :user
@@ -7,6 +11,9 @@ class AbuseReport < ActiveRecord::Base
validates :message, presence: true
validates :user_id, uniqueness: { message: 'has already been reported' }
+ # For CacheMarkdownField
+ alias_method :author, :reporter
+
def remove_user(deleted_by:)
user.block
DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true)
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 4cf8dd9a8ce..e4106e1c2e9 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,4 +1,8 @@
class Appearance < ActiveRecord::Base
+ include CacheMarkdownField
+
+ cache_markdown_field :description
+
validates :title, presence: true
validates :description, presence: true
validates :logo, file_size: { maximum: 1.megabyte }
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 55d2e07de08..c99aa7772bb 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -1,5 +1,7 @@
class ApplicationSetting < ActiveRecord::Base
+ include CacheMarkdownField
include TokenAuthenticatable
+
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
@@ -17,6 +19,11 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_whitelist, Array
serialize :domain_blacklist, Array
+ cache_markdown_field :sign_in_text
+ cache_markdown_field :help_page_text
+ cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
+ cache_markdown_field :after_sign_up_text
+
attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
validates :session_expire_delay,
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 61498140f27..cb40f33932a 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -1,6 +1,9 @@
class BroadcastMessage < ActiveRecord::Base
+ include CacheMarkdownField
include Sortable
+ cache_markdown_field :message, pipeline: :broadcast_message
+
validates :message, presence: true
validates :starts_at, presence: true
validates :ends_at, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 09c5223c60d..fd762b8c5ce 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1,6 +1,7 @@
module Ci
class Build < CommitStatus
include TokenAuthenticatable
+ include AfterCommitQueue
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
@@ -75,25 +76,20 @@ module Ci
state_machine :status do
after_transition pending: :running do |build|
- build.execute_hooks
+ build.run_after_commit do
+ BuildHooksWorker.perform_async(id)
+ end
end
after_transition any => [:success, :failed, :canceled] do |build|
- build.update_coverage
- build.execute_hooks
+ build.run_after_commit do
+ BuildFinishedWorker.perform_async(id)
+ end
end
after_transition any => [:success] do |build|
- if build.environment.present?
- service = CreateDeploymentService.new(
- build.project, build.user,
- environment: build.environment,
- sha: build.sha,
- ref: build.ref,
- tag: build.tag,
- options: build.options.to_h[:environment],
- variables: build.variables)
- service.execute(build)
+ build.run_after_commit do
+ BuildSuccessWorker.perform_async(id)
end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2cf9892edc5..4fdb5fef4fb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -3,6 +3,7 @@ module Ci
extend Ci::Model
include HasStatus
include Importable
+ include AfterCommitQueue
self.table_name = 'ci_commits'
@@ -56,6 +57,10 @@ module Ci
pipeline.finished_at = Time.now
end
+ before_transition do |pipeline|
+ pipeline.update_duration
+ end
+
after_transition [:created, :pending] => :running do |pipeline|
MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
@@ -66,12 +71,16 @@ module Ci
update_all(latest_build_finished_at: pipeline.finished_at)
end
- before_transition do |pipeline|
- pipeline.update_duration
+ after_transition [:created, :pending, :running] => :success do |pipeline|
+ pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
end
after_transition do |pipeline, transition|
- pipeline.execute_hooks unless transition.loopback?
+ next if transition.loopback?
+
+ pipeline.run_after_commit do
+ PipelineHooksWorker.perform_async(id)
+ end
end
end
@@ -292,11 +301,9 @@ module Ci
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
def merge_requests
- @merge_requests ||=
- begin
- project.merge_requests.where(source_branch: self.ref).
- select { |merge_request| merge_request.pipeline.try(:id) == self.id }
- end
+ @merge_requests ||= project.merge_requests
+ .where(source_branch: self.ref)
+ .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
end
private
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9fa8d17e74e..7b554be4f9a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,6 +1,7 @@
class CommitStatus < ActiveRecord::Base
include HasStatus
include Importable
+ include AfterCommitQueue
self.table_name = 'ci_builds'
@@ -85,25 +86,24 @@ class CommitStatus < ActiveRecord::Base
end
after_transition do |commit_status, transition|
- commit_status.pipeline.try do |pipeline|
- break if transition.loopback?
-
- if commit_status.complete?
- ProcessPipelineWorker.perform_async(pipeline.id)
+ next if transition.loopback?
+
+ commit_status.run_after_commit do
+ pipeline.try do |pipeline|
+ if complete?
+ PipelineProcessWorker.perform_async(pipeline.id)
+ else
+ PipelineUpdateWorker.perform_async(pipeline.id)
+ end
end
-
- UpdatePipelineWorker.perform_async(pipeline.id)
end
-
- true
- end
-
- after_transition [:created, :pending, :running] => :success do |commit_status|
- MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end
after_transition any => :failed do |commit_status|
- MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status)
+ commit_status.run_after_commit do
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(pipeline.project, nil).execute(self)
+ end
end
end
diff --git a/app/models/compare.rb b/app/models/compare.rb
index 4856510f526..3a8bbcb1acd 100644
--- a/app/models/compare.rb
+++ b/app/models/compare.rb
@@ -11,9 +11,10 @@ class Compare
end
end
- def initialize(compare, project)
+ def initialize(compare, project, straight: false)
@compare = compare
@project = project
+ @straight = straight
end
def commits
@@ -45,6 +46,18 @@ class Compare
end
end
+ def start_commit_sha
+ start_commit.try(:sha)
+ end
+
+ def base_commit_sha
+ base_commit.try(:sha)
+ end
+
+ def head_commit_sha
+ commit.try(:sha)
+ end
+
def raw_diffs(*args)
@compare.diffs(*args)
end
@@ -58,9 +71,9 @@ class Compare
def diff_refs
Gitlab::Diff::DiffRefs.new(
- base_sha: base_commit.try(:sha),
- start_sha: start_commit.try(:sha),
- head_sha: commit.try(:sha)
+ base_sha: @straight ? start_commit_sha : base_commit_sha,
+ start_sha: start_commit_sha,
+ head_sha: head_commit_sha
)
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
new file mode 100644
index 00000000000..90bd6490a02
--- /dev/null
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -0,0 +1,131 @@
+# This module takes care of updating cache columns for Markdown-containing
+# fields. Use like this in the body of your class:
+#
+# include CacheMarkdownField
+# cache_markdown_field :foo
+# cache_markdown_field :bar
+# cache_markdown_field :baz, pipeline: :single_line
+#
+# Corresponding foo_html, bar_html and baz_html fields should exist.
+module CacheMarkdownField
+ # Knows about the relationship between markdown and html field names, and
+ # stores the rendering contexts for the latter
+ class FieldData
+ extend Forwardable
+
+ def initialize
+ @data = {}
+ end
+
+ def_delegators :@data, :[], :[]=
+ def_delegator :@data, :keys, :markdown_fields
+
+ def html_field(markdown_field)
+ "#{markdown_field}_html"
+ end
+
+ def html_fields
+ markdown_fields.map {|field| html_field(field) }
+ end
+ end
+
+ # Dynamic registries don't really work in Rails as it's not guaranteed that
+ # every class will be loaded, so hardcode the list.
+ CACHING_CLASSES = %w[
+ AbuseReport
+ Appearance
+ ApplicationSetting
+ BroadcastMessage
+ Issue
+ Label
+ MergeRequest
+ Milestone
+ Namespace
+ Note
+ Project
+ Release
+ Snippet
+ ]
+
+ def self.caching_classes
+ CACHING_CLASSES.map(&:constantize)
+ end
+
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_reader :cached_markdown_fields do
+ FieldData.new
+ end
+
+ # Returns the default Banzai render context for the cached markdown field.
+ def banzai_render_context(field)
+ raise ArgumentError.new("Unknown field: #{field.inspect}") unless
+ cached_markdown_fields.markdown_fields.include?(field)
+
+ # Always include a project key, or Banzai complains
+ project = self.project if self.respond_to?(:project)
+ context = cached_markdown_fields[field].merge(project: project)
+
+ # Banzai is less strict about authors, so don't always have an author key
+ context[:author] = self.author if self.respond_to?(:author)
+
+ context
+ end
+
+ # Allow callers to look up the cache field name, rather than hardcoding it
+ def markdown_cache_field_for(field)
+ raise ArgumentError.new("Unknown field: #{field}") unless
+ cached_markdown_fields.markdown_fields.include?(field)
+
+ cached_markdown_fields.html_field(field)
+ end
+
+ # Always exclude _html fields from attributes (including serialization).
+ # They contain unredacted HTML, which would be a security issue
+ alias_method :attributes_before_markdown_cache, :attributes
+ def attributes
+ attrs = attributes_before_markdown_cache
+
+ cached_markdown_fields.html_fields.each do |field|
+ attrs.delete(field)
+ end
+
+ attrs
+ end
+ end
+
+ class_methods do
+ private
+
+ # Specify that a field is markdown. Its rendered output will be cached in
+ # a corresponding _html field. Any custom rendering options may be provided
+ # as a context.
+ def cache_markdown_field(markdown_field, context = {})
+ raise "Add #{self} to CacheMarkdownField::CACHING_CLASSES" unless
+ CacheMarkdownField::CACHING_CLASSES.include?(self.to_s)
+
+ cached_markdown_fields[markdown_field] = context
+
+ html_field = cached_markdown_fields.html_field(markdown_field)
+ cache_method = "#{markdown_field}_cache_refresh".to_sym
+ invalidation_method = "#{html_field}_invalidated?".to_sym
+
+ define_method(cache_method) do
+ html = Banzai::Renderer.cacheless_render_field(self, markdown_field)
+ __send__("#{html_field}=", html)
+ true
+ end
+
+ # The HTML becomes invalid if any dependent fields change. For now, assume
+ # author and project invalidate the cache in all circumstances.
+ define_method(invalidation_method) do
+ changed_fields = changed_attributes.keys
+ invalidations = changed_fields & [markdown_field.to_s, "author", "project"]
+ !invalidations.empty?
+ end
+
+ before_save cache_method, if: invalidation_method
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index ff465d2c745..c4b42ad82c7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,6 +6,7 @@
#
module Issuable
extend ActiveSupport::Concern
+ include CacheMarkdownField
include Participable
include Mentionable
include Subscribable
@@ -13,6 +14,9 @@ module Issuable
include Awardable
included do
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
+
belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index be295487fd2..8ed4a56b19b 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -2,6 +2,8 @@ class CycleAnalytics
include Gitlab::Database::Median
include Gitlab::Database::DateTime
+ DEPLOYMENT_METRIC_STAGES = %i[production staging]
+
def initialize(project, from:)
@project = project
@from = from
@@ -66,7 +68,7 @@ class CycleAnalytics
# cycle analytics stage.
interval_query = Arel::Nodes::As.new(
cte_table,
- subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s))
+ subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s))
median_datetime(cte_table, interval_query, name)
end
@@ -75,7 +77,7 @@ class CycleAnalytics
# closes the given issue) with issue and merge request metrics included. The metrics
# are loaded with an inner join, so issues / merge requests without metrics are
# automatically excluded.
- def base_query
+ def base_query_for(name)
arel_table = MergeRequestsClosingIssues.arel_table
# Load issues
@@ -91,7 +93,11 @@ class CycleAnalytics
join(MergeRequest::Metrics.arel_table).
on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
- # Limit to merge requests that have been deployed to production after `@from`
- query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
+ if DEPLOYMENT_METRIC_STAGES.include?(name)
+ # Limit to merge requests that have been deployed to production after `@from`
+ query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
+ end
+
+ query
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 070c76339b1..054d54f124e 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base
def includes_commit?(commit)
return false unless commit
- project.repository.is_ancestor?(commit.id, sha)
+ # Before 8.10, deployments didn't have keep-around refs. Any deployment
+ # created before then could have a `sha` referring to a commit that no
+ # longer exists in the repository, so just ignore those.
+ begin
+ project.repository.is_ancestor?(commit.id, sha)
+ rescue Rugged::OdbError
+ false
+ end
end
def update_merge_request_metrics!
@@ -90,6 +97,10 @@ class Deployment < ActiveRecord::Base
close_action.present?
end
+ def formatted_deployment_time
+ created_at.to_time.in_time_zone.to_s(:medium)
+ end
+
private
def ref_path
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 6ec498ea2b7..07f14a7ad8d 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -66,7 +66,22 @@ class Environment < ActiveRecord::Base
self.name == "production"
end
+ def first_deployment_for(commit)
+ ref = project.repository.ref_name_for_sha(ref_path, commit.sha)
+
+ return nil unless ref
+
+ deployment_id = ref.split('/').last
+ deployments.find(deployment_id)
+ end
+
def ref_path
"refs/environments/#{Shellwords.shellescape(name)}"
end
+
+ def formatted_external_url
+ return nil unless external_url
+
+ external_url.gsub(/\A.*?:\/\//, '')
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 633019fe0af..0764cb8cabd 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -68,8 +68,10 @@ class Event < ActiveRecord::Base
true
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
+ elsif merge_request? || merge_request_note?
+ Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
else
- ((merge_request? || note?) && target.present?) || milestone?
+ milestone?
end
end
@@ -280,6 +282,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_issue?
end
+ def merge_request_note?
+ note? && target && target.for_merge_request?
+ end
+
def project_snippet_note?
target.for_snippet?
end
@@ -335,7 +341,7 @@ class Event < ActiveRecord::Base
# update the project. Only one query should actually perform the update,
# hence we add the extra WHERE clause for last_activity_at.
Project.unscoped.where(id: project_id).
- where('last_activity_at > ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
+ where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago).
update_all(last_activity_at: created_at)
end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index ddd4bad5c21..698a7bbd327 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -4,6 +4,10 @@ class GlobalLabel
delegate :color, :description, to: :@first_label
+ def for_display
+ @first_label
+ end
+
def self.build_collection(labels)
labels = labels.group_by(&:title)
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index bda2b5c5d5d..cde4a568577 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -4,6 +4,10 @@ class GlobalMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title
+ def for_display
+ @first_milestone
+ end
+
def self.build_collection(milestones)
milestones = milestones.group_by(&:title)
@@ -17,6 +21,7 @@ class GlobalMilestone
@title = title
@name = title
@milestones = milestones
+ @first_milestone = milestones.find {|m| m.description.present? } || milestones.first
end
def safe_title
diff --git a/app/models/label.rb b/app/models/label.rb
index a23140b7d64..e8e12e2904e 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,4 +1,5 @@
class Label < ActiveRecord::Base
+ include CacheMarkdownField
include Referable
include Subscribable
@@ -8,6 +9,8 @@ class Label < ActiveRecord::Base
None = LabelStruct.new('No Label', 'No Label')
Any = LabelStruct.new('Any Label', '')
+ cache_markdown_field :description, pipeline: :single_line
+
DEFAULT_COLOR = '#428BCA'
default_value_for :color, DEFAULT_COLOR
diff --git a/app/models/member.rb b/app/models/member.rb
index 38a278ea559..b89ba8ecbb8 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -103,7 +103,12 @@ class Member < ActiveRecord::Base
}
if member.request?
- ::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute
+ ::Members::ApproveAccessRequestService.new(
+ source,
+ current_user,
+ id: member.id,
+ access_level: access_level
+ ).execute
else
member.save
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 071dfe54ef9..5ccfe11a2a2 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -31,7 +31,7 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars
# when creating new merge request
- attr_accessor :can_be_created, :compare_commits, :compare
+ attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
state_machine :state, initial: :opened do
event :close do
@@ -196,7 +196,7 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
- merge_request_diff.size
+ diffs(diff_options).size
end
def diff_base_commit
@@ -688,12 +688,15 @@ class MergeRequest < ActiveRecord::Base
def environments
return [] unless diff_head_commit
- environments = source_project.environments_for(
- source_branch, diff_head_commit)
- environments += target_project.environments_for(
- target_branch, diff_head_commit, with_tags: true)
+ @environments ||=
+ begin
+ environments = source_project.environments_for(
+ source_branch, diff_head_commit)
+ environments += target_project.environments_for(
+ target_branch, diff_head_commit, with_tags: true)
- environments.uniq
+ environments.uniq
+ end
end
def state_human_name
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 36b8b70870b..b8a10b7968e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
+ # Valid types of serialized diffs allowed by Gitlab::Git::Diff
+ VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
+
belongs_to :merge_request
state_machine :state, initial: :empty do
@@ -164,12 +167,24 @@ class MergeRequestDiff < ActiveRecord::Base
self == merge_request.merge_request_diff
end
- def compare_with(sha)
- CompareService.new.execute(project, head_commit_sha, project, sha)
+ def compare_with(sha, straight: true)
+ # When compare merge request versions we want diff A..B instead of A...B
+ # so we handle cases when user does squash and rebase of the commits between versions.
+ # For this reason we set straight to true by default.
+ CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
end
private
+ # Old GitLab implementations may have generated diffs as ["--broken-diff"].
+ # Avoid an error 500 by ignoring bad elements. See:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776
+ def valid_raw_diff?(raw)
+ return false unless raw.respond_to?(:each)
+
+ raw.any? { |element| VALID_CLASSES.include?(element.class) }
+ end
+
def dump_commits(commits)
commits.map(&:to_hash)
end
@@ -200,7 +215,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def load_diffs(raw, options)
- if raw.respond_to?(:each)
+ if valid_raw_diff?(raw)
if paths = options[:paths]
raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 44c3cbb2c73..23aecbfa3a6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -6,12 +6,16 @@ class Milestone < ActiveRecord::Base
Any = MilestoneStruct.new('Any Milestone', '', -1)
Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
+ include CacheMarkdownField
include InternalId
include Sortable
include Referable
include StripAttribute
include Milestoneish
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
+
belongs_to :project
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 919b3b1f095..b67049f0f55 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,9 +1,12 @@
class Namespace < ActiveRecord::Base
acts_as_paranoid
+ include CacheMarkdownField
include Sortable
include Gitlab::ShellAdapter
+ cache_markdown_field :description, pipeline: :description
+
has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User"
@@ -58,15 +61,13 @@ class Namespace < ActiveRecord::Base
def clean_path(path)
path = path.dup
# Get the email username by removing everything after an `@` sign.
- path.gsub!(/@.*\z/, "")
- # Usernames can't end in .git, so remove it.
- path.gsub!(/\.git\z/, "")
- # Remove dashes at the start of the username.
- path.gsub!(/\A-+/, "")
- # Remove periods at the end of the username.
- path.gsub!(/\.+\z/, "")
+ path.gsub!(/@.*\z/, "")
# Remove everything that's not in the list of allowed characters.
- path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ # Remove trailing violations ('.atom', '.git', or '.')
+ path.gsub!(/(\.atom|\.git|\.)*\z/, "")
+ # Remove leading violations ('-')
+ path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter.
diff --git a/app/models/note.rb b/app/models/note.rb
index f2656df028b..2d644b03e4d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -6,10 +6,13 @@ class Note < ActiveRecord::Base
include Awardable
include Importable
include FasterCacheKeys
+ include CacheMarkdownField
+
+ cache_markdown_field :note, pipeline: :note
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
- attr_accessor :note_html
+ attr_accessor :redacted_note_html
# An Array containing the number of visible references as generated by
# Banzai::ObjectRenderer
diff --git a/app/models/project.rb b/app/models/project.rb
index 12705f9ae48..2e8073733d4 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -6,6 +6,7 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Gitlab::CurrentSettings
include AccessRequestable
+ include CacheMarkdownField
include Referable
include Sortable
include AfterCommitQueue
@@ -15,8 +16,13 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
+ class BoardLimitExceeded < StandardError; end
+
+ NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'
+ cache_markdown_field :description, pipeline: :description
+
delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
default_value_for :archived, false
@@ -62,8 +68,7 @@ class Project < ActiveRecord::Base
belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
-
- has_one :board, dependent: :destroy
+ has_many :boards, before_add: :validate_board_limit, dependent: :destroy
# Project services
has_many :services
@@ -372,19 +377,9 @@ class Project < ActiveRecord::Base
%r{(?<project>#{name_pattern}/#{name_pattern})}
end
- def trending(since = 1.month.ago)
- # By counting in the JOIN we don't expose the GROUP BY to the outer query.
- # This means that calls such as "any?" and "count" just return a number of
- # the total count, instead of the counts grouped per project as a Hash.
- join_body = "INNER JOIN (
- SELECT project_id, COUNT(*) AS amount
- FROM notes
- WHERE created_at >= #{sanitize(since)}
- AND system IS FALSE
- GROUP BY project_id
- ) join_note_counts ON projects.id = join_note_counts.project_id"
-
- joins(join_body).reorder('join_note_counts.amount DESC')
+ def trending
+ joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
+ reorder('trending_projects.id ASC')
end
def cached_count
@@ -495,7 +490,7 @@ class Project < ActiveRecord::Base
end
def import_url
- if import_data && super
+ if import_data && super.present?
import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
import_url.full_url
else
@@ -834,11 +829,6 @@ class Project < ActiveRecord::Base
end
end
- def update_merge_requests(oldrev, newrev, ref, user)
- MergeRequests::RefreshService.new(self, user).
- execute(oldrev, newrev, ref)
- end
-
def valid_repo?
repository.exists?
rescue
@@ -1346,4 +1336,8 @@ class Project < ActiveRecord::Base
shared_projects.any?
end
+
+ def validate_board_limit(board)
+ raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
+ end
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 7613cbdea93..db46def11eb 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base
belongs_to :group
validates :project_id, presence: true
- validates :group_id, presence: true
+ validates :group, presence: true
validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
diff --git a/app/models/release.rb b/app/models/release.rb
index e196b84eb18..c936899799e 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,4 +1,8 @@
class Release < ActiveRecord::Base
+ include CacheMarkdownField
+
+ cache_markdown_field :description
+
belongs_to :project
validates :description, :project, :tag, presence: true
diff --git a/app/models/repository.rb b/app/models/repository.rb
index eb574555df6..72e473871fa 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -111,8 +111,10 @@ class Repository
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
ref ||= root_ref
- # Limited to 1000 commits for now, could be parameterized?
- args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
+ args = %W(
+ #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
+ --max-count #{limit} --grep=#{query} --regexp-ignore-case
+ )
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
@@ -717,6 +719,14 @@ class Repository
end
end
+ def ref_name_for_sha(ref_path, sha)
+ args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
+
+ # Not found -> ["", 0]
+ # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
+ Gitlab::Popen.popen(args, path_to_repo).first.split.last
+ end
+
def refs_contains_sha(ref_type, sha)
args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
names = Gitlab::Popen.popen(args, path_to_repo).first
@@ -838,6 +848,52 @@ class Repository
end
end
+ def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil)
+ update_branch_with_hooks(user, branch) do |ref|
+ index = rugged.index
+ parents = []
+ branch = find_branch(ref)
+
+ if branch
+ last_commit = branch.target
+ index.read_tree(last_commit.raw_commit.tree)
+ parents = [last_commit.sha]
+ end
+
+ actions.each do |action|
+ case action[:action]
+ when :create, :update, :move
+ mode =
+ case action[:action]
+ when :update
+ index.get(action[:file_path])[:mode]
+ when :move
+ index.get(action[:previous_path])[:mode]
+ end
+ mode ||= 0o100644
+
+ index.remove(action[:previous_path]) if action[:action] == :move
+
+ content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content]
+ oid = rugged.write(content, :blob)
+
+ index.add(path: action[:file_path], oid: oid, mode: mode)
+ when :delete
+ index.remove(action[:file_path])
+ end
+ end
+
+ options = {
+ tree: index.write_tree(rugged),
+ message: message,
+ parents: parents
+ }
+ options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
+
+ Rugged::Commit.create(rugged, options)
+ end
+ end
+
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
author = Gitlab::Git::committer_hash(email: email, name: name) || committer
@@ -968,7 +1024,8 @@ class Repository
root_ref_commit = commit(root_ref)
if branch_commit
- is_ancestor?(branch_commit.id, root_ref_commit.id)
+ same_head = branch_commit.id == root_ref_commit.id
+ !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
else
nil
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 8a1730f3f36..2373b445009 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -1,11 +1,21 @@
class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Linguist::BlobHelper
+ include CacheMarkdownField
include Participable
include Referable
include Sortable
include Awardable
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :content
+
+ # If file_name changes, it invalidates content
+ alias_method :default_content_html_invalidator, :content_html_invalidated?
+ def content_html_invalidated?
+ default_content_html_invalidator || file_name_changed?
+ end
+
default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: 'User'
diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb
new file mode 100644
index 00000000000..27e3732da17
--- /dev/null
+++ b/app/models/trending_project.rb
@@ -0,0 +1,35 @@
+class TrendingProject < ActiveRecord::Base
+ belongs_to :project
+
+ # The number of months to include in the trending calculation.
+ MONTHS_TO_INCLUDE = 1
+
+ # The maximum number of projects to include in the trending set.
+ PROJECTS_LIMIT = 100
+
+ # Populates the trending projects table with the current list of trending
+ # projects.
+ def self.refresh!
+ # The calculation **must** run in a transaction. If the removal of data and
+ # insertion of new data were to run separately a user might end up with an
+ # empty list of trending projects for a short period of time.
+ transaction do
+ delete_all
+
+ timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago)
+
+ connection.execute <<-EOF.strip_heredoc
+ INSERT INTO #{table_name} (project_id)
+ SELECT project_id
+ FROM notes
+ INNER JOIN projects ON projects.id = notes.project_id
+ WHERE notes.created_at >= #{timestamp}
+ AND notes.system IS FALSE
+ AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}
+ GROUP BY project_id
+ ORDER BY count(*) DESC
+ LIMIT #{PROJECTS_LIMIT};
+ EOF
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 508efd85050..f367f4616fb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -589,6 +589,11 @@ class User < ActiveRecord::Base
end
def set_projects_limit
+ # `User.select(:id)` raises
+ # `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
+ # without this safeguard!
+ return unless self.has_attribute?(:projects_limit)
+
connection_default_value_defined = new_record? && !projects_limit_changed?
return unless self.projects_limit.nil? || connection_default_value_defined
@@ -902,7 +907,7 @@ class User < ActiveRecord::Base
if domain_matches?(allowed_domains, self.email)
valid = true
else
- error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}"
+ error = "domain is not authorized for sign-up"
valid = false
end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index be25c750d67..be4721d7a51 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
- can! :read_merge_request
can! :read_note
can! :create_project
can! :create_issue
@@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy
can! :read_pipeline
can! :read_environment
can! :read_deployment
+ can! :read_merge_request
end
# Permissions given when an user is team member of a project
@@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy
can! :admin_milestone
can! :admin_project_snippet
can! :admin_project_member
- can! :admin_merge_request
can! :admin_note
can! :admin_wiki
can! :admin_project
@@ -118,6 +117,7 @@ class ProjectPolicy < BasePolicy
can! :read_container_image
can! :build_download_code
can! :build_read_container_image
+ can! :read_merge_request
end
def owner_access!
@@ -139,11 +139,18 @@ class ProjectPolicy < BasePolicy
def team_access!(user)
access = project.team.max_member_access(user.id)
- guest_access! if access >= Gitlab::Access::GUEST
- reporter_access! if access >= Gitlab::Access::REPORTER
- team_member_reporter_access! if access >= Gitlab::Access::REPORTER
- developer_access! if access >= Gitlab::Access::DEVELOPER
- master_access! if access >= Gitlab::Access::MASTER
+ return if access < Gitlab::Access::GUEST
+ guest_access!
+
+ return if access < Gitlab::Access::REPORTER
+ reporter_access!
+ team_member_reporter_access!
+
+ return if access < Gitlab::Access::DEVELOPER
+ developer_access!
+
+ return if access < Gitlab::Access::MASTER
+ master_access!
end
def archived_access!
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 0c208150fb8..1a2bad77a02 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -56,9 +56,8 @@ class BaseService
result
end
- def success
- {
- status: :success
- }
+ def success(pass_back = {})
+ pass_back[:status] = :success
+ pass_back
end
end
diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb
deleted file mode 100644
index b2069ca825a..00000000000
--- a/app/services/boards/base_service.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Boards
- class BaseService < ::BaseService
- delegate :board, to: :project
- end
-end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 072a0749285..9bdd7b6f0cf 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -1,16 +1,21 @@
module Boards
- class CreateService < Boards::BaseService
+ class CreateService < BaseService
def execute
- create_board! unless project.board.present?
- project.board
+ if project.boards.empty?
+ create_board!
+ else
+ project.boards.first
+ end
end
private
def create_board!
- project.create_board
- project.board.lists.create(list_type: :backlog)
- project.board.lists.create(list_type: :done)
+ board = project.boards.create
+ board.lists.create(list_type: :backlog)
+ board.lists.create(list_type: :done)
+
+ board
end
end
end
diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb
new file mode 100644
index 00000000000..c0d7ff5b585
--- /dev/null
+++ b/app/services/boards/issues/create_service.rb
@@ -0,0 +1,23 @@
+module Boards
+ module Issues
+ class CreateService < BaseService
+ def execute
+ create_issue(params.merge(label_ids: [list.label_id]))
+ end
+
+ private
+
+ def board
+ @board ||= project.boards.find(params.delete(:board_id))
+ end
+
+ def list
+ @list ||= board.lists.find(params.delete(:list_id))
+ end
+
+ def create_issue(params)
+ ::Issues::CreateService.new(project, current_user, params).execute
+ end
+ end
+ end
+end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 34efd09ed9f..fd4a462c7b2 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -1,6 +1,6 @@
module Boards
module Issues
- class ListService < Boards::BaseService
+ class ListService < BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless list.movable?
@@ -10,6 +10,10 @@ module Boards
private
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def list
@list ||= board.lists.find(params[:id])
end
@@ -36,12 +40,7 @@ module Boards
end
def set_state
- params[:state] =
- case list.list_type.to_sym
- when :backlog then 'opened'
- when :done then 'closed'
- else 'all'
- end
+ params[:state] = list.done? ? 'closed' : 'opened'
end
def board_label_ids
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 84dc3f70e76..96554a92a02 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -1,6 +1,6 @@
module Boards
module Issues
- class MoveService < Boards::BaseService
+ class MoveService < BaseService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
return false unless valid_move?
@@ -10,6 +10,10 @@ module Boards
private
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def valid_move?
moving_from_list.present? && moving_to_list.present? &&
moving_from_list != moving_to_list
@@ -49,7 +53,7 @@ module Boards
if moving_to_list.movable?
moving_from_list.label_id
else
- board.lists.movable.pluck(:label_id)
+ project.boards.joins(:lists).merge(List.movable).pluck(:label_id)
end
Array(label_ids).compact
diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb
new file mode 100644
index 00000000000..84f1fc3a4e2
--- /dev/null
+++ b/app/services/boards/list_service.rb
@@ -0,0 +1,14 @@
+module Boards
+ class ListService < BaseService
+ def execute
+ create_board! if project.boards.empty?
+ project.boards
+ end
+
+ private
+
+ def create_board!
+ Boards::CreateService.new(project, current_user).execute
+ end
+ end
+end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index b1887820bd4..abc7aeece39 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -1,23 +1,23 @@
module Boards
module Lists
- class CreateService < Boards::BaseService
- def execute
+ class CreateService < BaseService
+ def execute(board)
List.transaction do
label = project.labels.find(params[:label_id])
- position = next_position
+ position = next_position(board)
- create_list(label, position)
+ create_list(board, label, position)
end
end
private
- def next_position
+ def next_position(board)
max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ
end
- def create_list(label, position)
+ def create_list(board, label, position)
board.lists.create(label: label, list_type: :label, position: position)
end
end
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index 25da3bfb56d..f986e05944c 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -1,9 +1,11 @@
module Boards
module Lists
- class DestroyService < Boards::BaseService
+ class DestroyService < BaseService
def execute(list)
return false unless list.destroyable?
+ @board = list.board
+
list.with_lock do
decrement_higher_lists(list)
remove_list(list)
@@ -12,6 +14,8 @@ module Boards
private
+ attr_reader :board
+
def decrement_higher_lists(list)
board.lists.movable.where('position > ?', list.position)
.update_all('position = position - 1')
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index 1c48b9786e4..d8048f1c67e 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -1,11 +1,11 @@
module Boards
module Lists
- class GenerateService < Boards::BaseService
- def execute
+ class GenerateService < BaseService
+ def execute(board)
return false unless board.lists.movable.empty?
List.transaction do
- label_params.each { |params| create_list(params) }
+ label_params.each { |params| create_list(board, params) }
end
true
@@ -13,9 +13,9 @@ module Boards
private
- def create_list(params)
+ def create_list(board, params)
label = find_or_create_label(params)
- Lists::CreateService.new(project, current_user, label_id: label.id).execute
+ Lists::CreateService.new(project, current_user, label_id: label.id).execute(board)
end
def find_or_create_label(params)
@@ -25,10 +25,8 @@ module Boards
def label_params
[
- { name: 'Development', color: '#5CB85C' },
- { name: 'Testing', color: '#F0AD4E' },
- { name: 'Production', color: '#FF5F00' },
- { name: 'Ready', color: '#FF0000' }
+ { name: 'To Do', color: '#F0AD4E' },
+ { name: 'Doing', color: '#5CB85C' }
]
end
end
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
new file mode 100644
index 00000000000..c579ed4c869
--- /dev/null
+++ b/app/services/boards/lists/list_service.rb
@@ -0,0 +1,9 @@
+module Boards
+ module Lists
+ class ListService < BaseService
+ def execute(board)
+ board.lists
+ end
+ end
+ end
+end
diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb
index 020ff69f4a7..f2a68865f7b 100644
--- a/app/services/boards/lists/move_service.rb
+++ b/app/services/boards/lists/move_service.rb
@@ -1,7 +1,8 @@
module Boards
module Lists
- class MoveService < Boards::BaseService
+ class MoveService < BaseService
def execute(list)
+ @board = list.board
@old_position = list.position
@new_position = params[:position]
@@ -16,7 +17,7 @@ module Boards
private
- attr_reader :old_position, :new_position
+ attr_reader :board, :old_position, :new_position
def valid_move?
new_position.present? && new_position != old_position &&
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 36c93dddadb..d3dd30b2588 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -16,6 +16,8 @@ module Ci
process_stage(index)
end
+ @pipeline.update_status
+
# Return a flag if a when builds got enqueued
new_builds.flatten.any?
end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 6d6075628af..5e8fafca98c 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,7 +3,7 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
- def execute(source_project, source_branch, target_project, target_branch)
+ def execute(source_project, source_branch, target_project, target_branch, straight: false)
source_commit = source_project.commit(source_branch)
return unless source_commit
@@ -23,9 +23,10 @@ class CompareService
raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
- source_sha
+ source_sha,
+ straight
)
- Compare.new(raw_compare, target_project)
+ Compare.new(raw_compare, target_project, straight: straight)
end
end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index c87542e57a2..923de58b244 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -2,31 +2,42 @@ require_relative 'base_service'
class CreateDeploymentService < BaseService
def execute(deployable = nil)
- environment = find_or_create_environment
+ return unless executable?
- if close?
- environment.close
- return
+ ActiveRecord::Base.transaction do
+ @deployable = deployable
+ @environment = prepare_environment
+
+ if close?
+ @environment.close
+ return
+ end
+
+ @environment.reopen
+
+ deploy.tap do |deployment|
+ deployment.update_merge_request_metrics!
+ end
end
+ end
+
+ private
- environment.reopen
+ def executable?
+ project && name.present?
+ end
- deployment = project.deployments.create(
- environment: environment,
+ def deploy
+ project.deployments.create(
+ environment: @environment,
ref: params[:ref],
tag: params[:tag],
sha: params[:sha],
user: current_user,
- deployable: deployable
- )
-
- deployment.update_merge_request_metrics!
- deployment
+ deployable: @deployable)
end
- private
-
- def find_or_create_environment
+ def prepare_environment
project.environments.find_or_create_by(name: expanded_name) do |environment|
environment.external_url = expanded_url
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index e8465729d06..9bd4bd464f7 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -27,8 +27,9 @@ module Files
create_target_branch
end
- if commit
- success
+ result = commit
+ if result
+ success(result: result)
else
error('Something went wrong. Your changes were not committed')
end
@@ -42,6 +43,12 @@ module Files
@source_branch != @target_branch || @source_project != @project
end
+ def file_has_changed?
+ return false unless @last_commit_sha && last_commit
+
+ @last_commit_sha != last_commit.sha
+ end
+
def raise_error(message)
raise ValidationError.new(message)
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
new file mode 100644
index 00000000000..d28912e1301
--- /dev/null
+++ b/app/services/files/multi_service.rb
@@ -0,0 +1,124 @@
+require_relative "base_service"
+
+module Files
+ class MultiService < Files::BaseService
+ class FileChangedError < StandardError; end
+
+ def commit
+ repository.multi_action(
+ user: current_user,
+ branch: @target_branch,
+ message: @commit_message,
+ actions: params[:actions],
+ author_email: @author_email,
+ author_name: @author_name
+ )
+ end
+
+ private
+
+ def validate
+ super
+
+ params[:actions].each_with_index do |action, index|
+ unless action[:file_path].present?
+ raise_error("You must specify a file_path.")
+ end
+
+ regex_check(action[:file_path])
+ regex_check(action[:previous_path]) if action[:previous_path]
+
+ if project.empty_repo? && action[:action] != :create
+ raise_error("No files to #{action[:action]}.")
+ end
+
+ validate_file_exists(action)
+
+ case action[:action]
+ when :create
+ validate_create(action)
+ when :update
+ validate_update(action)
+ when :delete
+ validate_delete(action)
+ when :move
+ validate_move(action, index)
+ else
+ raise_error("Unknown action type `#{action[:action]}`.")
+ end
+ end
+ end
+
+ def validate_file_exists(action)
+ return if action[:action] == :create
+
+ file_path = action[:file_path]
+ file_path = action[:previous_path] if action[:action] == :move
+
+ blob = repository.blob_at_branch(params[:branch_name], file_path)
+
+ unless blob
+ raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.")
+ end
+ end
+
+ def last_commit
+ Gitlab::Git::Commit.last_for_path(repository, @source_branch, @file_path)
+ end
+
+ def regex_check(file)
+ if file =~ Gitlab::Regex.directory_traversal_regex
+ raise_error(
+ 'Your changes could not be committed, because the file name, `' +
+ file +
+ '` ' +
+ Gitlab::Regex.directory_traversal_regex_message
+ )
+ end
+
+ unless file =~ Gitlab::Regex.file_path_regex
+ raise_error(
+ 'Your changes could not be committed, because the file name, `' +
+ file +
+ '` ' +
+ Gitlab::Regex.file_path_regex_message
+ )
+ end
+ end
+
+ def validate_create(action)
+ return if project.empty_repo?
+
+ if repository.blob_at_branch(params[:branch_name], action[:file_path])
+ raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
+ end
+ end
+
+ def validate_delete(action)
+ end
+
+ def validate_move(action, index)
+ if action[:previous_path].nil?
+ raise_error("You must supply the original file path when moving file `#{action[:file_path]}`.")
+ end
+
+ blob = repository.blob_at_branch(params[:branch_name], action[:file_path])
+
+ if blob
+ raise_error("Move destination `#{action[:file_path]}` already exists.")
+ end
+
+ if action[:content].nil?
+ blob = repository.blob_at_branch(params[:branch_name], action[:previous_path])
+ blob.load_all_data!(repository) if blob.truncated?
+ params[:actions][index][:content] = blob.data
+ end
+ end
+
+ def validate_update(action)
+ if file_has_changed?
+ raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
+ end
+ end
+ end
+end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 9e9b5b63f26..c17fdb8d1f1 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -23,12 +23,6 @@ module Files
end
end
- def file_has_changed?
- return false unless @last_commit_sha && last_commit
-
- @last_commit_sha != last_commit.sha
- end
-
def last_commit
@last_commit ||= Gitlab::Git::Commit.
last_for_path(@source_project.repository, @source_branch, @file_path)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index c499427605a..e8415862de5 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -63,13 +63,12 @@ class GitPushService < BaseService
protected
def update_merge_requests
- @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
+ UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
EventCreateService.new.push(@project, current_user, build_push_data)
- SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
- Ci::CreatePipelineService.new(project, current_user, build_push_data).execute
+ Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute
ProjectCacheWorker.perform_async(@project.id)
end
@@ -148,16 +147,6 @@ class GitPushService < BaseService
push_commits)
end
- def build_push_data_system_hook
- @push_data_system ||= Gitlab::DataBuilder::Push.build(
- @project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- [])
- end
-
def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index 566049525cb..d572a928a42 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -2,14 +2,14 @@ module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails
def execute(commit_status)
- each_merge_request(commit_status) do |merge_request|
+ commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request)
end
end
# Closes any pending build failed todos for the parent MRs when a build is retried
def close(commit_status)
- each_merge_request(commit_status) do |merge_request|
+ commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_retried(merge_request)
end
end
diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb
new file mode 100644
index 00000000000..f636e5fec4f
--- /dev/null
+++ b/app/services/merge_requests/assign_issues_service.rb
@@ -0,0 +1,35 @@
+module MergeRequests
+ class AssignIssuesService < BaseService
+ def assignable_issues
+ @assignable_issues ||= begin
+ if current_user == merge_request.author
+ closes_issues.select do |issue|
+ !issue.assignee_id? && can?(current_user, :admin_issue, issue)
+ end
+ else
+ []
+ end
+ end
+ end
+
+ def execute
+ assignable_issues.each do |issue|
+ Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue)
+ end
+
+ {
+ count: assignable_issues.count
+ }
+ end
+
+ private
+
+ def merge_request
+ params[:merge_request]
+ end
+
+ def closes_issues
+ @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user)
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index d0d155b7ee1..58f69a41e14 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -42,28 +42,33 @@ module MergeRequests
super(:merge_request)
end
- def merge_request_from(commit_status)
- branches = commit_status.ref
+ def merge_requests_for(branch)
+ origin_merge_requests = @project.origin_merge_requests
+ .opened.where(source_branch: branch).to_a
- # This is for ref-less builds
- branches ||= @project.repository.branch_names_contains(commit_status.sha)
+ fork_merge_requests = @project.fork_merge_requests
+ .opened.where(source_branch: branch).to_a
- return [] if branches.blank?
+ (origin_merge_requests + fork_merge_requests)
+ .uniq.select(&:source_project)
+ end
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
- merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
+ def pipeline_merge_requests(pipeline)
+ merge_requests_for(pipeline.ref).each do |merge_request|
+ next unless pipeline == merge_request.pipeline
- merge_requests.uniq.select(&:source_project)
+ yield merge_request
+ end
end
- def each_merge_request(commit_status)
- merge_request_from(commit_status).each do |merge_request|
+ def commit_status_merge_requests(commit_status)
+ merge_requests_for(commit_status.ref).each do |merge_request|
pipeline = merge_request.pipeline
next unless pipeline
next unless pipeline.sha == commit_status.sha
- yield merge_request, pipeline
+ yield merge_request
end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index e57791f6818..404f75616b5 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -4,7 +4,7 @@ module MergeRequests
merge_request = MergeRequest.new(params)
# Set MR attributes
- merge_request.can_be_created = false
+ merge_request.can_be_created = true
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
@@ -22,6 +22,12 @@ module MergeRequests
return build_failed(merge_request, message)
end
+ if merge_request.source_project == merge_request.target_project &&
+ merge_request.target_branch == merge_request.source_branch
+
+ return build_failed(merge_request, 'You must select different branches')
+ end
+
compare = CompareService.new.execute(
merge_request.source_project,
merge_request.source_branch,
@@ -29,17 +35,8 @@ module MergeRequests
merge_request.target_branch,
)
- commits = compare.commits
-
- # At this point we decide if merge request can be created
- # If we have at least one commit to merge -> creation allowed
- if commits.present?
- merge_request.compare_commits = commits
- merge_request.can_be_created = true
- merge_request.compare = compare
- else
- merge_request.can_be_created = false
- end
+ merge_request.compare_commits = compare.commits
+ merge_request.compare = compare
set_title_and_description(merge_request)
end
@@ -89,6 +86,8 @@ module MergeRequests
end
end
+ merge_request.title = merge_request.wip_title if commits.empty?
+
merge_request
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
index 4ad5fb08311..dc159de0058 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -18,12 +18,13 @@ module MergeRequests
merge_request.save
end
- # Triggers the automatic merge of merge_request once the build succeeds
- def trigger(commit_status)
- each_merge_request(commit_status) do |merge_request, pipeline|
+ # Triggers the automatic merge of merge_request once the pipeline succeeds
+ def trigger(pipeline)
+ return unless pipeline.success?
+
+ pipeline_merge_requests(pipeline) do |merge_request|
next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable?
- next unless pipeline.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 9dbec49d163..a37cc3fdf21 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -15,7 +15,10 @@ module MergeRequests
params.except!(:target_branch, :force_remove_source_branch)
end
- merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
+ if params[:force_remove_source_branch].present?
+ merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
+ end
+
handle_wip_event(merge_request)
update(merge_request)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index de8049b8e2e..72712afc07e 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -475,10 +475,12 @@ class NotificationService
end
def reject_users_without_access(recipients, target)
- return recipients unless target.is_a?(Issue)
+ return recipients unless target.is_a?(Issuable)
+
+ ability = :"read_#{target.to_ability_name}"
recipients.select do |user|
- user.can?(:read_issue, target)
+ user.can?(ability, target)
end
end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 1725a30fae5..e4ae3dec8aa 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -122,7 +122,12 @@ module SlashCommands
command :label do |labels_param|
label_ids = find_label_ids(labels_param)
- @updates[:add_label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:add_label_ids] ||= []
+ @updates[:add_label_ids] += label_ids
+
+ @updates[:add_label_ids].uniq!
+ end
end
desc 'Remove all or specific label(s)'
@@ -136,7 +141,12 @@ module SlashCommands
if labels_param.present?
label_ids = find_label_ids(labels_param)
- @updates[:remove_label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:remove_label_ids] ||= []
+ @updates[:remove_label_ids] += label_ids
+
+ @updates[:remove_label_ids].uniq!
+ end
else
@updates[:label_ids] = []
end
@@ -152,7 +162,12 @@ module SlashCommands
command :relabel do |labels_param|
label_ids = find_label_ids(labels_param)
- @updates[:label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:label_ids] ||= []
+ @updates[:label_ids] += label_ids
+
+ @updates[:label_ids].uniq!
+ end
end
desc 'Add a todo'
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 776530ac0a5..f8e6b2ef094 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -273,12 +273,12 @@ class TodoService
end
def reject_users_without_access(users, project, target)
- if target.is_a?(Note) && target.for_issue?
+ if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?)
target = target.noteable
end
- if target.is_a?(Issue)
- select_users(users, :read_issue, target)
+ if target.is_a?(Issuable)
+ select_users(users, :"read_#{target.to_ability_name}", target)
else
select_users(users, :read_project, project)
end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index 4dc3b2ab9a0..2821ecf0a88 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator
projects
public
repository
+ robots.txt
s
search
services
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index 56bf6194914..05f3d9a3b50 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -21,7 +21,7 @@
%td
%strong.subheading.visible-xs-block.visible-sm-block Message
.message
- = markdown(abuse_report.message.squish!, pipeline: :single_line, author: reporter)
+ = markdown_field(abuse_report, :message)
%td
- if user
= link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
index 6c51639b840..acbe17036f7 100644
--- a/app/views/admin/appearances/preview.html.haml
+++ b/app/views/admin/appearances/preview.html.haml
@@ -1,9 +1,12 @@
-- page_title "Preview | Appearance"
+= render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box
- .login-heading
- %h3 Existing user? Sign in
- %form
- = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
- = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
- = button_tag "Sign in", class: "btn-create btn"
+ %form.show-gl-field-errors
+ .form-group
+ = label_tag :login
+ = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
+ .form-group
+ = label_tag :password
+ = password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.'
+ .form-group
+ = button_tag "Sign in", class: "btn-create btn"
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 0d79ca7dc52..c4c68cd7891 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -221,7 +221,11 @@
%fieldset
%legend Metrics
%p
- These settings require a restart to take effect.
+ Setup InfluxDB to measure a wide variety of statistics like the time spent
+ in running SQL queries. These settings require a
+ = link_to 'restart', help_page_path('administration/restart_gitlab')
+ to take effect.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index f952d2e9aa1..3132d157f29 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -1,7 +1,10 @@
.broadcast-message-preview{ style: broadcast_message_style(@broadcast_message) }
= icon('bullhorn')
.js-broadcast-message-preview
- = render_broadcast_message(@broadcast_message.message.presence || "Your message here")
+ - if @broadcast_message.message.present?
+ = render_broadcast_message(@broadcast_message)
+ - else
+ = "Your message here"
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
= form_errors(@broadcast_message)
diff --git a/app/views/admin/broadcast_messages/preview.js.haml b/app/views/admin/broadcast_messages/preview.js.haml
index fbc9453c72e..c72e59640d7 100644
--- a/app/views/admin/broadcast_messages/preview.js.haml
+++ b/app/views/admin/broadcast_messages/preview.js.haml
@@ -1 +1 @@
-$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@message))}");
+$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 77a11e49e20..adfa1eaafc9 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -23,4 +23,4 @@
- if group.description.present?
.description
- = markdown(group.description, pipeline: :description)
+ = markdown_field(group, :description)
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index f417b2e44a4..be224d66855 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,7 +1,7 @@
%li{id: dom_id(label)}
.label-row
= render_colored_label(label, tooltip: false)
- = markdown(label.description, pipeline: :single_line)
+ = markdown_field(label, :description)
.pull-right
= link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
= link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 1e755785d90..339cfc613fe 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -87,7 +87,7 @@
- if project.description.present?
.description
- = markdown(project.description, pipeline: :description)
+ = markdown_field(project, :description)
= paginate @projects, theme: 'gitlab'
- else
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index a5e82e55cc1..10fea1996aa 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -71,7 +71,7 @@
.col-md-6
%h4 Recent builds served by this Runner
- %table.table.builds.runner-builds
+ %table.table.ci-table.runner-builds
%thead
%tr
%th Build
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 9d31f31c639..2a0302638ba 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -55,7 +55,7 @@
= sort_options_hash[@sort]
- else
= sort_title_recently_created
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to todos_filter_path(sort: sort_value_priority) do
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
index 73c3a3dd2eb..20cd7b0179d 100644
--- a/app/views/devise/confirmations/almost_there.haml
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -3,9 +3,9 @@
Almost there...
%p.lead
Please check your email to confirm your account
-- if after_sign_up_text.present?
+- if current_application_settings.after_sign_up_text.present?
.well-confirmation.text-center
- = markdown(after_sign_up_text)
+ = markdown_field(current_application_settings, :after_sign_up_text)
%p.confirmation-content.text-center
No confirmation email received? Please check your spam folder or
.append-bottom-20.prepend-top-20.text-center
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 970ba147111..5d25dd398d6 100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -1,14 +1,14 @@
+= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box
- .login-heading
- %h3 Resend confirmation instructions
.login-body
- = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f|
+ = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
- .clearfix.append-bottom-20
- = f.email_field :email, placeholder: 'Email', class: "form-control", required: true
+ .form-group
+ = f.label :email
+ = f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.'
.clearfix
- = f.submit "Resend confirmation instructions", class: 'btn btn-success'
+ = f.submit "Resend", class: 'btn btn-success'
.clearfix.prepend-top-20
= render 'devise/shared/sign_in_link'
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 56048e99c17..b518fae7c95 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -1,19 +1,21 @@
+= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
- .login-heading
- %h3 Change your password
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
- %div
- = f.password_field :password, class: "form-control top", placeholder: "New password", required: true
- %div
- = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
+ .form-group
+ = f.label 'New password', for: :password
+ = f.password_field :password, class: "form-control top", required: true, title: 'This field is required'
+ .form-group
+ = f.label 'Confirm new password', for: :password_confirmation
+ = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', required: true
.clearfix
= f.submit "Change your password", class: "btn btn-primary"
.clearfix.prepend-top-20
%p
- = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
- = render 'devise/shared/sign_in_link'
+ %span.light Didn't receive a confirmation email?
+ = link_to "Request a new one", new_confirmation_path(resource_name)
+= render 'devise/shared/sign_in_link'
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 535e85869e5..1fcfd06419a 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -1,12 +1,12 @@
+= render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box
- .login-heading
- %h3 Reset password
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
- .clearfix.append-bottom-20
- = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true
+ .form-group
+ = f.label :email
+ = f.email_field :email, class: "form-control", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.'
.clearfix
= f.submit "Reset password", class: "btn-primary btn"
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 9f5520603cd..a96b579c593 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,6 +1,10 @@
-= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
- = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off"
- = f.password_field :password, class: "form-control bottom", placeholder: "Password"
+= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f|
+ %div.form-group
+ = f.label "Username or email", for: :login
+ = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+ %div.form-group
+ = f.label :password
+ = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required."
- if devise_mapping.rememberable?
.remember-me.checkbox
%label{for: "user_remember_me"}
@@ -8,5 +12,5 @@
%span Remember me
.pull-right
= link_to "Forgot your password?", new_password_path(resource_name)
- %div
+ %div.submit-container
= f.submit "Sign in", class: "btn btn-save"
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index b7d3acac2b1..e82a08cdb0c 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -1,6 +1,10 @@
-= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do
- = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"}
- = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
+= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do
+ .form-group
+ = label_tag 'Username or email', for: :username
+ = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
+ .form-group
+ = label_tag :password
+ = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true }
- if devise_mapping.rememberable?
.remember-me.checkbox
%label{for: "remember_me"}
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 2ef383960f4..b26efbb4535 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,6 +1,10 @@
-= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do
- = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
- = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
+= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do
+ .form-group
+ = label_tag "#{server['label']} Login", for: :username
+ = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
+ .form-group
+ = label_tag :password
+ = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true }
- if devise_mapping.rememberable?
.remember-me.checkbox
%label{for: "remember_me"}
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index 28194506acc..fa8e7979461 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,19 +1,22 @@
- page_title "Sign in"
%div
- - if signin_enabled? || ldap_enabled? || crowd_enabled?
- = render 'devise/shared/signin_box'
+ - if form_based_providers.any?
+ = render 'devise/shared/tabs_ldap'
+ - else
+ = render 'devise/shared/tabs_normal'
+ .tab-content
+ - if signin_enabled? || ldap_enabled? || crowd_enabled?
+ = render 'devise/shared/signin_box'
- -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
- .clearfix.prepend-top-20
- = render 'devise/shared/omniauth_box'
-
- -# Signup only makes sense if you can also sign-in
- - if signin_enabled? && signup_enabled?
- .prepend-top-20
+ -# Signup only makes sense if you can also sign-in
+ - if signin_enabled? && signup_enabled?
= render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled
- if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div
No authentication methods configured.
+
+ - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
+ .clearfix
+ = render 'devise/shared/omniauth_box'
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index e623f7cff88..0e865b807c1 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -3,20 +3,19 @@
= page_specific_javascript_tag('u2f.js')
%div
+ = render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication'
.login-box
- .login-heading
- %h3 Two-Factor Authentication
.login-body
- if @user.two_factor_otp_enabled?
- %h5 Authenticate via Two-Factor App
- = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
+ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
- = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off'
- %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
- .prepend-top-20
- = f.submit "Verify code", class: "btn btn-save"
+ .form-group
+ = f.label 'Two-Factor Authentication code', name: :otp_attempt
+ = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.'
+ %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
+ .prepend-top-20
+ = f.submit "Verify code", class: "btn btn-save"
- if @user.two_factor_u2f_enabled?
- %hr
= render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name }
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 2e7da2747d0..8908b64cdac 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -1,8 +1,9 @@
-%p
- %span.light
- Sign in with &nbsp;
- - providers = enabled_button_based_providers
- - providers.each do |provider|
+%div.omniauth-container
+ %p
%span.light
- - has_icon = provider_has_icon?(provider)
- = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
+ Sign in with &nbsp;
+ - providers = enabled_button_based_providers
+ - providers.each do |provider|
+ %span.light
+ - has_icon = provider_has_icon?(provider)
+ = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml
index fafc4b82f53..289bf40f3de 100644
--- a/app/views/devise/shared/_sign_in_link.html.haml
+++ b/app/views/devise/shared/_sign_in_link.html.haml
@@ -1,5 +1,4 @@
%p
%span.light
Already have login and password?
- %strong
= link_to "Sign in", new_session_path(resource_name)
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index 2c15e2c4891..810dd5ab687 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -1,32 +1,15 @@
-.login-box
- - if signup_enabled?
- .login-heading
- %h3 Existing user? Sign in
- - else
- .login-heading
- %h3 Sign in
+#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' }
.login-body
- if form_based_providers.any?
- %ul.nav-links
- - if crowd_enabled?
- %li.active
- = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab'
- - @ldap_servers.each_with_index do |server, i|
- %li{class: (:active if i.zero? && !crowd_enabled?)}
- = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- - if signin_enabled?
- %li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
- .tab-content
- - if crowd_enabled?
- %div.tab-pane.active{id: "tab-crowd"}
- = render 'devise/sessions/new_crowd'
- - @ldap_servers.each_with_index do |server, i|
- %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)}
- = render 'devise/sessions/new_ldap', server: server
- - if signin_enabled?
- %div#tab-signin.tab-pane
- = render 'devise/sessions/new_base'
+ - if crowd_enabled?
+ %div.tab-pane.active{id: "tab-crowd"}
+ = render 'devise/sessions/new_crowd'
+ - @ldap_servers.each_with_index do |server, i|
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)}
+ = render 'devise/sessions/new_ldap', server: server
+ - if signin_enabled?
+ %div#tab-signin.tab-pane
+ = render 'devise/sessions/new_base'
- elsif signin_enabled?
= render 'devise/sessions/new_base'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 905a8dbcd84..d0bbcf3115e 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,29 +1,30 @@
-.login-box
- - if signin_enabled?
- .login-heading
- %h3 New user? Create an account
- - else
- .login-heading
- %h3 Create an account
+#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body
- = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= devise_error_messages!
- %div
- = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
- %div
- = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
- %div
- = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
+ %div.form-group
+ = f.label :name
+ = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
+ %div.username.form-group
+ = f.label :username
+ = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
+ %p.validation-error.hide Username is already taken.
+ %p.validation-success.hide Username is available.
+ %p.validation-pending.hide Checking username availability...
+ %div.form-group
+ = f.label :email
+ = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address."
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters"
+ = f.label :password
+ = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
+ %p.gl-field-hint Minimum length is #{@minimum_password_length} characters
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
%div
- = f.submit "Sign up", class: "btn-create btn"
-
-.clearfix.prepend-top-20
+ = f.submit "Register", class: "btn-register btn"
+.clearfix.submit-container
%p
%span.light Didn't receive a confirmation email?
= succeed '.' do
diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml
new file mode 100644
index 00000000000..f943d25e41a
--- /dev/null
+++ b/app/views/devise/shared/_tab_single.html.haml
@@ -0,0 +1,3 @@
+%ul.nav-links.nav-tabs.new-session-tabs.single-tab
+ %li.active
+ %a= tab_title
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
new file mode 100644
index 00000000000..e276e91433a
--- /dev/null
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -0,0 +1,10 @@
+%ul.new-session-tabs.nav-links.nav-tabs
+ - if crowd_enabled?
+ %li.active
+ = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab'
+ - @ldap_servers.each_with_index do |server, i|
+ %li{class: (:active if i.zero? && !crowd_enabled?)}
+ = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
+ - if signin_enabled?
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
new file mode 100644
index 00000000000..79b1d447a92
--- /dev/null
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -0,0 +1,5 @@
+%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'}
+ %li.active{ role: 'presentation' }
+ %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in
+ %li{ role: 'presentation'}
+ %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index 49c087c0646..49b2f77111f 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -1,12 +1,12 @@
+= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box
- .login-heading
- %h3 Resend unlock email
.login-body
- = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
+ = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
- .clearfix.append-bottom-20
- = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off'
+ .form-group.append-bottom-20
+ = f.label :email
+ = f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.'
.clearfix
= f.submit 'Resend unlock instructions', class: 'btn btn-success'
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index b8248a80a27..a1b39d9e1a0 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -23,7 +23,7 @@
= sort_options_hash[@sort]
- else
= sort_title_recently_created
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to explore_groups_path(sort: sort_value_recently_created) do
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 132bbe26fe0..4cff14b096b 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -7,7 +7,7 @@
= visibility_level_label(params[:visibility_level].to_i)
- else
Any
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_projects_path(visibility_level: nil) do
@@ -27,7 +27,7 @@
= params[:tag]
- else
Any
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_projects_path(tag: nil) do
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index 2fb3190ab11..b185b81db7f 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -1,27 +1,22 @@
-= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
- .form-group
- = f.label :user_ids, "People", class: 'control-label'
- .col-sm-10
- = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
- .help-block
+= form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f|
+ .row
+ .col-md-4.col-lg-6
+ = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true)
+ .help-block.append-bottom-10
Search for users by name, username, or email, or invite new ones using their email address.
- .form-group
- = f.label :access_level, "Group Access", class: 'control-label'
- .col-sm-10
- = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
- .help-block
- Read more about role permissions
- %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
+ .col-md-3.col-lg-2
+ = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select"
+ .help-block.append-bottom-10
+ = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ about role permissions
- .form-group
- = f.label :expires_at, 'Access expiration date', class: 'control-label'
- .col-sm-10
+ .col-md-3.col-lg-2
.clearable-input
- = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
+ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- .help-block
+ .help-block.append-bottom-10
On this date, the user(s) will automatically lose access to this group and all of its projects.
- .form-actions
- = f.submit 'Add users to group', class: "btn btn-create"
+ .col-md-2
+ = f.submit 'Add to group', class: "btn btn-create btn-block"
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index f789796e942..ebf9aca7700 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,35 +1,31 @@
- page_title "Members"
-.group-members-page.prepend-top-default
+.project-members-page.prepend-top-default
+ %h4
+ Members
+ %hr
- if can?(current_user, :admin_group_member, @group)
- .panel.panel-default
- .panel-heading
- Add new user to group
- .panel-body
- %p.light
- Members of group have access to all group projects.
- .new-group-member-holder
- = render "new_group_member"
+ .project-members-new.append-bottom-default
+ %p.clearfix
+ Add new user to
+ %strong= @group.name
+ = render "new_group_member"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
+ .append-bottom-default.clearfix
+ %h5.member.existing-title
+ Existing users
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-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" }
+ = icon("search")
.panel.panel-default
.panel-heading
+ Users with access to
%strong #{@group.name}
- group members
%span.badge= @members.total_count
- .controls
- = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
- = button_tag class: 'btn', title: 'Search' do
- = icon("search")
%ul.content-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
-
-:javascript
- $('form.member-search-form').on('submit', function(event) {
- event.preventDefault();
- Turbolinks.visit(this.action + '?' + $(this).serialize());
- });
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 3be7ed8432c..de8f53b6b52 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,3 +1,3 @@
:plain
- $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
- new gl.MemberExpirationDate();
+ var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}');
+ $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 31db6ee0cad..fab61f447c2 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -21,7 +21,7 @@
- if @group.description.present?
.cover-desc.description
- = markdown(@group.description, pipeline: :description)
+ = markdown_field(@group, :description)
%div.groups-header{ class: container_class }
.top-area
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 57601ae9be0..31631887317 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -20,7 +20,7 @@
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}.
- if current_application_settings.help_page_text.present?
%hr
- = markdown(current_application_settings.help_page_text)
+ = markdown_field(current_application_settings, :help_page_text)
%hr
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 67ff4b272b9..e138ebab018 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,7 +1,8 @@
- project = @target_project || @project
- noteable_type = @noteable.class if @noteable.present?
-:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
- GitLab.GfmAutoComplete.cachedData = undefined;
- GitLab.GfmAutoComplete.setup();
+- if project
+ :javascript
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.cachedData = undefined;
+ GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 15a94ac23c5..6c2285fa2b6 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -11,3 +11,4 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
+ = render "layouts/init_auto_complete" if @gfm_form
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 3d28eec84ef..6922f1e153f 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,36 +1,37 @@
!!! 5
-%html{ lang: "en"}
+%html{ lang: "en", class: "devise-layout-html"}
= render "layouts/head"
- %body.ui_charcoal.login-page.application.navless
- = Gon::Base.render_data
- = render "layouts/header/empty"
- = render "layouts/broadcast"
- .container.navless-container
- .content
- = render "layouts/flash"
- .row
- .col-sm-5.pull-right
- = yield
- .col-sm-7.brand-holder.pull-left
- %h1
- = brand_title
- - if brand_item
- = brand_image
- = brand_text
- - else
- %h3 Open source software to collaborate on code
+ %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page }}
+ .page-wrap
+ = Gon::Base.render_data
+ = render "layouts/header/empty"
+ = render "layouts/broadcast"
+ .container.navless-container
+ .content
+ = render "layouts/flash"
+ .row
+ .col-sm-5.pull-right.new-session-forms-container
+ = yield
+ .col-sm-7.brand-holder.pull-left
+ %h1
+ = brand_title
+ - if brand_item
+ = brand_image
+ = brand_text
+ - else
+ %h3 Open source software to collaborate on code
- %p
- Manage git repositories with fine grained access controls that keep your code secure.
- Perform code reviews and enhance collaboration with merge requests.
- Each project can also have an issue tracker and a wiki.
+ %p
+ Manage Git repositories with fine-grained access controls that keep your code secure.
+ Perform code reviews and enhance collaboration with merge requests.
+ Each project can also have an issue tracker and a wiki.
- - if extra_sign_in_text.present?
- = markdown(extra_sign_in_text)
+ - if current_application_settings.sign_in_text.present?
+ = markdown_field(current_application_settings, :sign_in_text)
- %hr
- .container
- .footer-links
- = link_to "Explore", explore_root_path
- = link_to "Help", help_path
- = link_to "About GitLab", "https://about.gitlab.com/"
+ %hr.footer-fixed
+ .container.footer-container
+ .footer-links
+ = link_to "Explore", explore_root_path
+ = link_to "Help", help_path
+ = link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 237280872f1..7faa8bded86 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -41,7 +41,7 @@
%li.header-user.dropdown
= link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
- %span.caret
+ = icon('caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index e44a2bfed9d..99a58bbb676 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -116,4 +116,4 @@
-# Shortcut to issue boards
%li.hidden
- = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index c80f22457b4..e2e974ba072 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -86,11 +86,11 @@
= f.label :username, "Path", class: "label-light"
.input-group
.input-group-addon
- = "#{root_url}u/"
+ = root_url
= f.text_field :username, required: true, class: 'form-control'
.help-block
Current path:
- = "#{root_url}u/#{current_user.username}"
+ = "#{root_url}#{current_user.username}"
.prepend-top-default
= f.button class: "btn btn-warning", type: "submit" do
= icon "spinner spin", class: "hidden loading-username"
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 8ef31ca3bda..5590198a20e 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -9,7 +9,7 @@
.project-home-desc
- if @project.description.present?
- = markdown(@project.description, pipeline: :description)
+ = markdown_field(@project, :description)
- if forked_from_project = @project.forked_from_project
%p
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index cb97181b9e1..0c8241053e7 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,3 +1,4 @@
+- @gfm_form = true
- supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false)
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
@@ -7,6 +8,3 @@
= text_area_tag attr, nil, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress')
-
-- content_for :scripts_body do
- = render "layouts/init_auto_complete" if current_user && (@target_project || @project)
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 5a98e258b22..dfb96305f48 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,45 +1,48 @@
+- @no_container = true
- page_title "Blame", @blob.path, @ref
+= render "projects/commits/head"
-%h3.page-title Blame view
+%div{ class: container_class }
+ %h3.page-title Blame view
-#blob-content-holder.tree-holder
- .file-holder
- .file-title
- = blob_icon @blob.mode, @blob.name
- %strong
- = @path
- %small= number_to_human_size @blob.size
- .file-actions
- = render "projects/blob/actions"
- .table-responsive.file-content.blame.code.js-syntax-highlight
- %table
- - current_line = 1
- - @blame_groups.each do |blame_group|
- %tr
- %td.blame-commit
- .commit
- - commit = blame_group[:commit]
- = author_avatar(commit, size: 36)
- .commit-row-title
- %strong
- = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
- .pull-right
- = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace"
- &nbsp;
- .light
- = commit_author_link(commit, avatar: false)
- authored
- #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
- %td.line-numbers
- - line_count = blame_group[:lines].count
- - (current_line...(current_line + line_count)).each do |i|
- %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
- = icon("link")
- = i
- \
- - current_line += line_count
- %td.lines
- %pre.code.highlight
- %code
- - blame_group[:lines].each do |line|
- #{line}
+ #blob-content-holder.tree-holder
+ .file-holder
+ .file-title
+ = blob_icon @blob.mode, @blob.name
+ %strong
+ = @path
+ %small= number_to_human_size @blob.size
+ .file-actions
+ = render "projects/blob/actions"
+ .table-responsive.file-content.blame.code.js-syntax-highlight
+ %table
+ - current_line = 1
+ - @blame_groups.each do |blame_group|
+ %tr
+ %td.blame-commit
+ .commit
+ - commit = blame_group[:commit]
+ = author_avatar(commit, size: 36)
+ .commit-row-title
+ %strong
+ = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
+ .pull-right
+ = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace"
+ &nbsp;
+ .light
+ = commit_author_link(commit, avatar: false)
+ authored
+ #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
+ %td.line-numbers
+ - line_count = blame_group[:lines].count
+ - (current_line...(current_line + line_count)).each do |i|
+ %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ = icon("link")
+ = i
+ \
+ - current_line += line_count
+ %td.lines
+ %pre.code.highlight
+ %code
+ - blame_group[:lines].each do |line|
+ #{line}
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 680e95ac6b5..2a0352a71b7 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,28 +1,31 @@
+- @no_container = true
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_tag('blob_edit/blob_edit_bundle.js')
+= render "projects/commits/head"
-- if @conflict
- .alert.alert-danger
- Someone edited the file the same time you did. Please check out
- = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
- and make sure your changes will not unintentionally remove theirs.
+%div{ class: container_class }
+ - if @conflict
+ .alert.alert-danger
+ Someone edited the file the same time you did. Please check out
+ = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
+ and make sure your changes will not unintentionally remove theirs.
-.file-editor
- %ul.nav-links.no-bottom.js-edit-mode
- %li.active
- = link_to '#editor' do
- Edit File
+ .file-editor
+ %ul.nav-links.no-bottom.js-edit-mode
+ %li.active
+ = link_to '#editor' do
+ Edit File
- %li
- = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
- = editing_preview_title(@blob.name)
+ %li
+ = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+ = editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
- = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
- = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
- = hidden_field_tag 'last_commit_sha', @last_commit_sha
- = hidden_field_tag 'content', '', id: "file-content"
- = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
- = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
+ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
+ = hidden_field_tag 'last_commit_sha', @last_commit_sha
+ = hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
+ = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index 73066150fb3..ba1502c97b6 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -12,8 +12,17 @@
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
{{ list.title }}
- %span.pull-right{ "v-if" => "list.type !== 'blank'" }
- {{ list.issuesSize }}
+ .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" }
+ %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" }
+ {{ list.issuesSize }}
+ - if can?(current_user, :admin_issue, @project)
+ %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
+ "@click" => "showNewIssueForm",
+ "v-if" => "list.type !== 'done'",
+ "aria-label" => "Add an issue",
+ "title" => "Add an issue",
+ data: { placement: "top", container: "body" } }
+ = icon("plus")
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
@@ -26,12 +35,38 @@
":issues" => "list.issues",
":loading" => "list.loading",
":disabled" => "disabled",
+ ":show-issue-form.sync" => "showIssueForm",
":issue-link-base" => "issueLinkBase" }
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
+ - if can? current_user, :create_issue, @project
+ %board-new-issue{ "inline-template" => true,
+ ":list" => "list",
+ ":show-issue-form.sync" => "showIssueForm",
+ "v-show" => "list.type !== 'done' && showIssueForm" }
+ .card.board-new-issue-form
+ %form{ "@submit" => "submit($event)" }
+ .flash-container{ "v-if" => "error" }
+ .flash-alert
+ An error occured. Please try again.
+ %label.label-light{ ":for" => "list.id + '-title'" }
+ Title
+ %input.form-control{ type: "text",
+ "v-model" => "title",
+ "v-el:input" => true,
+ ":id" => "list.id + '-title'" }
+ .clearfix.prepend-top-10
+ %button.btn.btn-success.pull-left{ type: "submit",
+ ":disabled" => "title === ''",
+ "v-el:submit-button" => true }
+ Submit issue
+ %button.btn.btn-default.pull-right{ type: "button",
+ "@click" => "cancel" }
+ Cancel
%ul.board-list{ "v-el:list" => true,
"v-show" => "!loading",
- ":data-board" => "list.id" }
+ ":data-board" => "list.id",
+ ":class" => "{ 'is-smaller': showIssueForm }" }
= render "projects/boards/components/card"
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml
index e8b60b54d80..d8f16022407 100644
--- a/app/views/projects/boards/components/_card.html.haml
+++ b/app/views/projects/boards/components/_card.html.haml
@@ -7,7 +7,7 @@
":issue-link-base" => "issueLinkBase",
":disabled" => "disabled",
"track-by" => "id" }
- %li.card{ ":class" => "{ 'user-can-drag': !disabled }",
+ %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }",
":index" => "index" }
%h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
@@ -15,7 +15,7 @@
":title" => "issue.title" }
{{ issue.title }}
.card-footer
- %span.card-number
+ %span.card-number{ "v-if" => "issue.id" }
= precede '#' do
{{ issue.id }}
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
@@ -26,7 +26,7 @@
":title" => "label.description",
data: { container: 'body' } }
{{ label.title }}
- %a.has-tooltip{ ":href" => "'/u/' + issue.assignee.username",
+ %a.has-tooltip{ ":href" => "'/' + issue.assignee.username",
":title" => "'Assigned to ' + issue.assignee.name",
"v-if" => "issue.assignee",
data: { container: 'body' } }
diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml
new file mode 100644
index 00000000000..885f8e34b55
--- /dev/null
+++ b/app/views/projects/boards/index.html.haml
@@ -0,0 +1,16 @@
+- @no_container = true
+- @content_class = "issue-boards-content"
+- page_title "Boards"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('boards/boards_bundle.js')
+ = page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
+
+= render "projects/issues/head"
+
+= render 'shared/issuable/filter', type: :boards
+
+.boards-list#board-app{ "v-cloak" => true, data: board_data }
+ .boards-app-loading.text-center{ "v-if" => "loading" }
+ = icon("spinner spin")
+ = render "projects/boards/components/board"
diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml
index edbbd3f3d2a..885f8e34b55 100644
--- a/app/views/projects/boards/show.html.haml
+++ b/app/views/projects/boards/show.html.haml
@@ -10,10 +10,7 @@
= render 'shared/issuable/filter', type: :boards
-.boards-list#board-app{ "v-cloak" => true,
- "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}",
- "data-disabled" => "#{!can?(current_user, :admin_list, @project)}",
- "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" }
+.boards-list#board-app{ "v-cloak" => true, data: board_data }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 5217b8bf028..4480b2f22c3 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -30,8 +30,8 @@
= render 'projects/buttons/download', project: @project, ref: branch.name
- - if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ - if can?(current_user, :push_code, @project)
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if branch.name != @repository.root_ref
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index e889f29c816..84f38575e84 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -15,7 +15,7 @@
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
= projects_sort_options_hash[@sort]
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_branches_path(sort: sort_value_name) do
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index f5344091cae..966633f1f89 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
- = icon('right-arrow')
+ = icon('arrow-right')
= ci_icon_for_status(build.status)
%span
- if build.name
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
index f3747ba2a21..36294c89fa8 100644
--- a/app/views/projects/builds/_table.html.haml
+++ b/app/views/projects/builds/_table.html.haml
@@ -5,7 +5,7 @@
.nothing-here-block No builds to show
- else
.table-holder
- %table.table.builds
+ %table.table.ci-table.builds-page
%thead
%tr
%th Status
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index e4d41288aa6..b5e8b0bf6eb 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,56 +1,59 @@
+- @no_container = true
- page_title "#{@build.name} (##{@build.id})", "Builds"
- trace_with_state = @build.trace_with_state
- header_title project_title(@project, "Builds", project_builds_path(@project))
+= render "projects/pipelines/head", build_subnav: true
-.build-page
- = render "header"
+%div{ class: container_class }
+ .build-page
+ = render "header"
- - if @build.stuck?
- - unless @build.any_runners_online?
- .bs-callout.bs-callout-warning
- %p
- - if no_runners_for_project?(@build.project)
- This build is stuck, because the project doesn't have any runners online assigned to it.
- - elsif @build.tags.any?
- This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
- - @build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - else
- This build is stuck, because you don't have any active runners that can run this build.
+ - if @build.stuck?
+ - unless @build.any_runners_online?
+ .bs-callout.bs-callout-warning
+ %p
+ - if no_runners_for_project?(@build.project)
+ This build is stuck, because the project doesn't have any runners online assigned to it.
+ - elsif @build.tags.any?
+ This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
+ - @build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - else
+ This build is stuck, because you don't have any active runners that can run this build.
- %br
- Go to
- = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
- Runners page
+ %br
+ Go to
+ = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
+ Runners page
- .prepend-top-default
- - if @build.active?
- .autoscroll-container
- %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
- - if @build.erased?
- .erased.alert.alert-warning
- - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
- Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
- - else
- #js-build-scroll.scroll-controls
- = link_to '#build-trace', class: 'btn' do
- %i.fa.fa-angle-up
- = link_to '#down-build-trace', class: 'btn' do
- %i.fa.fa-angle-down
- %pre.build-trace#build-trace
- %code.bash.js-build-output
- = icon("refresh spin", class: "js-build-refresh")
+ .prepend-top-default
+ - if @build.active?
+ .autoscroll-container
+ %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
+ - if @build.erased?
+ .erased.alert.alert-warning
+ - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
+ Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
+ - else
+ #js-build-scroll.scroll-controls
+ = link_to '#build-trace', class: 'btn' do
+ %i.fa.fa-angle-up
+ = link_to '#down-build-trace', class: 'btn' do
+ %i.fa.fa-angle-down
+ %pre.build-trace#build-trace
+ %code.bash.js-build-output
+ = icon("refresh spin", class: "js-build-refresh")
- #down-build-trace
+ #down-build-trace
-= render "sidebar"
+ = render "sidebar"
-:javascript
- new Build({
- page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}",
- build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}",
- build_status: "#{@build.status}",
- build_stage: "#{@build.stage}",
- state1: "#{trace_with_state[:state]}"
- })
+ :javascript
+ new Build({
+ page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}",
+ build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}",
+ build_status: "#{@build.status}",
+ build_stage: "#{@build.stage}",
+ state1: "#{trace_with_state[:state]}"
+ })
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 22db33498f1..29d549a60f5 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -5,10 +5,10 @@
= custom_icon('icon_fork')
%span Fork
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span Fork
%div.count-with-arrow
%span.arrow
- = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
+ = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do
= @project.forks_count
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 547bc0c9c19..017d3ff6af2 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -5,8 +5,10 @@
.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
- = render_status_with_link('build', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('build', subject.status)
.ci-status-text= subject.name
- else
- = render_status_with_link('build', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b87c7a485df..36eadbd2bf1 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -67,7 +67,7 @@
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= custom_icon('icon_play')
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
@@ -78,7 +78,7 @@
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 29d767e7769..6c82a4e5600 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -14,7 +14,7 @@
.dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
%span.hidden-xs Options
- %span.caret.commit-options-dropdown-caret
+ = icon('caret-down', class: ".commit-options-dropdown-caret")
%ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block
= link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
@@ -24,6 +24,8 @@
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
%li.clearfix
= cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ %li.clearfix
+ = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
%li.divider
%li.dropdown-header
Download
@@ -63,10 +65,10 @@
.commit-box.content-block
%h3.commit-title
- = markdown escape_once(@commit.title), pipeline: :single_line, author: @commit.author
+ = markdown(@commit.title, pipeline: :single_line, author: @commit.author)
- if @commit.description.present?
%pre.commit-description
- = preserve(markdown(escape_once(@commit.description), pipeline: :single_line, author: @commit.author))
+ = preserve(markdown(@commit.description, pipeline: :single_line, author: @commit.author))
:javascript
$(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 9258f4b3c25..d6916fb7f1a 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -1,45 +1,46 @@
-.row-content-block.build-content.middle-block.pipeline-actions
- .pull-right
- .btn.btn-grouped.btn-white.toggle-pipeline-btn
- %span.toggle-btn-text Hide
- %span pipeline graph
- %span.caret
- - if can?(current_user, :update_pipeline, pipeline.project)
- - if pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
+.pipeline-graph-container
+ .row-content-block.build-content.middle-block.pipeline-actions
+ .pull-right
+ .btn.btn-grouped.btn-white.toggle-pipeline-btn
+ %span.toggle-btn-text Hide
+ %span pipeline graph
+ %span.caret
+ - if can?(current_user, :update_pipeline, pipeline.project)
+ - if pipeline.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
- - if pipeline.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+ - if pipeline.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
- .oneline.clearfix
- - if defined?(pipeline_details) && pipeline_details
- Pipeline
- = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
- with
- = pluralize pipeline.statuses.count(:id), "build"
- - if pipeline.ref
- for
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
- - if pipeline.duration
- in
- = time_interval_in_words pipeline.duration
+ .oneline.clearfix
+ - if defined?(pipeline_details) && pipeline_details
+ Pipeline
+ = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
+ with
+ = pluralize pipeline.statuses.count(:id), "build"
+ - if pipeline.ref
+ for
+ = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
+ - if pipeline.duration
+ in
+ = time_interval_in_words pipeline.duration
-.row-content-block.build-content.middle-block.pipeline-graph
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
+ .row-content-block.build-content.middle-block.pipeline-graph.hidden
+ .pipeline-visualization
+ %ul.stage-column-list
+ - stages = pipeline.stages_with_latest_statuses
+ - stages.each do |stage, statuses|
+ %li.stage-column
+ .stage-name
+ %a{name: stage}
+ - if stage
+ = stage.titleize
+ .builds-container
+ %ul
+ = render "projects/commit/pipeline_stage", statuses: statuses
- if pipeline.yaml_errors.present?
@@ -55,7 +56,7 @@
\.gitlab-ci.yml not found in this commit
.table-holder.pipeline-holder
- %table.table.builds.pipeline
+ %table.table.ci-table.pipeline
%thead
%tr
%th Status
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
index 23c5c51fbc2..289aa5178b1 100644
--- a/app/views/projects/commit/_pipeline_stage.html.haml
+++ b/app/views/projects/commit/_pipeline_stage.html.haml
@@ -10,5 +10,5 @@
- else
%li.build
.curve
- .build-content
+ .dropdown.inline.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml
index 4e7a6f1af08..5d0d5ba0262 100644
--- a/app/views/projects/commit/_pipeline_status_group.html.haml
+++ b/app/views/projects/commit/_pipeline_status_group.html.haml
@@ -1,11 +1,13 @@
- group_status = CommitStatus.where(id: subject).status
-= render_status_with_link('build', group_status)
-.dropdown.inline
- %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
- %span.ci-status-text
- = name
- %span.badge= subject.size
- %ul.dropdown-menu.grouped-pipeline-dropdown
- .arrow
+%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+ %span.ci-status-icon
+ = render_status_with_link('build', group_status)
+ %span.ci-status-text
+ = name
+ %span.badge= subject.size
+.dropdown-menu.grouped-pipeline-dropdown
+ .arrow
+ %ul
- subject.each do |status|
- = render "projects/#{status.to_partial_path}_pipeline", subject: status
+ %li
+ = render "projects/#{status.to_partial_path}_pipeline", subject: status
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 998812793a2..640651e93f5 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -4,7 +4,7 @@
.nothing-here-block No pipelines to show
- else
.table-holder
- %table.table.builds
+ %table.table.ci-table
%tbody
%th Status
%th Pipeline
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 2f051fb90e0..f9d7eac3542 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -1,7 +1,10 @@
+- @no_container = true
- page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits"
+= render "projects/commits/head"
-.prepend-top-default
- = render "commit_box"
+%div{ class: container_class }
+ .prepend-top-default
+ = render "commit_box"
-= render "ci_menu"
-= render "builds"
+ = render "ci_menu"
+ = render "builds"
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index ed44d86a687..cebf58d63df 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,14 +1,17 @@
+- @no_container = true
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
+= render "projects/commits/head"
-.prepend-top-default
- = render "commit_box"
-- if @commit.status
- = render "ci_menu"
-- else
- %div.block-connector
-= render "projects/diffs/diffs", diffs: @diffs
-= render "projects/notes/notes_with_form"
-- if can_collaborate_with_project?
- - %w(revert cherry-pick).each do |type|
- = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
+%div{ class: container_class }
+ .prepend-top-default
+ = render "commit_box"
+ - if @commit.status
+ = render "ci_menu"
+ - else
+ %div.block-connector
+ = render "projects/diffs/diffs", diffs: @diffs
+ = render "projects/notes/notes_with_form"
+ - if can_collaborate_with_project?
+ - %w(revert cherry-pick).each do |type|
+ = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 389477d0927..fb48aef0559 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -33,7 +33,7 @@
- if commit.description?
%pre.commit-row-description.js-toggle-content
- = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
+ = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
.commit-row-info
= commit_author_link(commit, avatar: false, size: 24)
diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml
index 27d928c87a0..05fb37cdc0f 100644
--- a/app/views/projects/compare/_ref_dropdown.html.haml
+++ b/app/views/projects/compare/_ref_dropdown.html.haml
@@ -1,5 +1,5 @@
.dropdown-menu.dropdown-menu-selectable
- = dropdown_title "Select branch/tag"
- = dropdown_filter "Filter by branch/tag"
+ = dropdown_title "Select Git revision"
+ = dropdown_filter "Filter by Git revision"
= dropdown_content
= dropdown_loading
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index e9ff8e90dd5..45be6581cfc 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -4,7 +4,7 @@
%div{ class: container_class }
.sub-header-block
- Compare branches, tags or commit ranges.
+ Compare Git revisions.
%br
Fill input field with commit id like
%code.label-branch 4eedf23
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 7b7e37353e3..387ff018073 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -1,12 +1,18 @@
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
+
+ - external_url = deployment.environment.external_url
+ - if external_url
+ = link_to external_url, target: '_blank', class: 'btn external-url' do
+ = icon('external-link')
+
- actions = deployment.manual_actions
- if actions.present?
.inline
.dropdown
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= custom_icon('icon_play')
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
%li
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index fce7b1cb34e..ca0005abd0c 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -5,11 +5,13 @@
%td
= render 'projects/deployments/commit', deployment: deployment
- %td
+ %td.build-column
- if deployment.deployable
- = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
- = user_avatar(user: deployment.user, size: 20)
+ = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
+ - if deployment.user
+ by
+ = user_avatar(user: deployment.user, size: 20)
%td
#{time_ago_with_tooltip(deployment.created_at)}
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 576e7ef021a..067cf595da3 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,4 +1,5 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
+- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
.content-block.oneline-block.files-changed
@@ -20,7 +21,7 @@
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
-.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, diffs.project))}}
+.files{ data: { can_create_note: can_create_note } }
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file)
- blob = diff_file.blob(diff_commit)
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index d07de45fdde..257e0a855bd 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -8,7 +8,7 @@
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
-
+ = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option')
- if editable_diff?(diff_file)
- link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {}
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index a04d53e02bf..c8f84b96cb7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -91,7 +91,7 @@
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
- = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
+ = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' }
- if Gitlab.config.registry.enabled
.form-group
@@ -100,7 +100,8 @@
= f.check_box :container_registry_enabled
%strong Container Registry
%br
- %span.descr Enable Container Registry for this repository
+ %span.descr Enable Container Registry for this project
+ = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
= render 'merge_request_settings', f: f
%hr
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index 205392b1e81..19c536897bd 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -4,10 +4,17 @@
%td
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
- %td
+ %td.deployment-column
- if last_deployment
- = user_avatar(user: last_deployment.user, size: 20)
- %strong ##{last_deployment.id}
+ %span ##{last_deployment.iid}
+ - if last_deployment.user
+ by
+ = user_avatar(user: last_deployment.user, size: 20)
+
+ %td
+ - if last_deployment && last_deployment.deployable
+ = link_to [@project.namespace.becomes(Namespace), @project, last_deployment.deployable], class: 'build-link' do
+ = "#{last_deployment.deployable.name} (##{last_deployment.deployable.id})"
%td
- if last_deployment
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
index 6d1bdb9320f..3871165763c 100644
--- a/app/views/projects/environments/edit.html.haml
+++ b/app/views/projects/environments/edit.html.haml
@@ -1,6 +1,9 @@
+- @no_container = true
- page_title "Edit", @environment.name, "Environments"
+= render "projects/pipelines/head"
-%h3.page-title
- Edit environment
-%hr
-= render 'form'
+%div{ class: container_class }
+ %h3.page-title
+ Edit environment
+ %hr
+ = render 'form'
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 11e2ff506b4..f0b64a8775b 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -22,25 +22,27 @@
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- - if @all_environments.blank?
- .blank-state.blank-state-no-icon
- %h2.blank-state-title
- You don't have any environments right now.
- %p.blank-state-text
- Environments are places where code gets deployed, such as staging or production.
- %br
- = succeed "." do
- = link_to "Read more about environments", help_page_path("ci/environments")
- - if can?(current_user, :create_environment, @project)
- = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
- New environment
- - else
- .table-holder
- %table.table.builds.environments
- %tbody
- %th Environment
- %th Last Deployment
- %th Commit
- %th
- %th.hidden-xs
- = render @environments
+ .environments-container
+ - if @environments.blank?
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any environments right now.
+ %p.blank-state-text
+ Environments are places where code gets deployed, such as staging or production.
+ %br
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci/environments")
+ - if can?(current_user, :create_environment, @project)
+ = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
+ New environment
+ - else
+ .table-holder
+ %table.table.ci-table.environments
+ %tbody
+ %th Environment
+ %th Last Deployment
+ %th Build
+ %th Commit
+ %th
+ %th.hidden-xs
+ = render @environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index e51667ade2d..24638c77cbb 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,6 +1,9 @@
+- @no_container = true
- page_title 'New Environment'
+= render "projects/pipelines/head"
-%h3.page-title
- New environment
-%hr
-= render 'form'
+%div{ class: container_class }
+ %h3.page-title
+ New environment
+ %hr
+ = render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 4981b95a19b..c4209d499ad 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -15,26 +15,27 @@
- if @environment.opened? && last_deployment.try(:close_action)
= link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post
- - if @deployments.blank?
- .blank-state.blank-state-no-icon
- %h2.blank-state-title
- You don't have any deployments right now.
- %p.blank-state-text
- Define environments in the deploy stage(s) in
- %code .gitlab-ci.yml
- to track deployments here.
- = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- - else
- .table-holder
- %table.table.builds.environments
- %thead
- %tr
- %th ID
- %th Commit
- %th Build
- %th
- %th.hidden-xs
+ .deployments-container
+ - if @deployments.blank?
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any deployments right now.
+ %p.blank-state-text
+ Define environments in the deploy stage(s) in
+ %code .gitlab-ci.yml
+ to track deployments here.
+ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
+ - else
+ .table-holder
+ %table.table.ci-table.environments
+ %thead
+ %tr
+ %th ID
+ %th Commit
+ %th Build
+ %th
+ %th.hidden-xs
- = render @deployments
+ = render @deployments
- = paginate @deployments, theme: 'gitlab'
+ = paginate @deployments, theme: 'gitlab'
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index bacc5708e4b..abf4f697f86 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -15,7 +15,7 @@
= sort_options_hash[@sort]
- else
= sort_title_recently_created
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
- excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 331dc1fcc29..80fe6be49b0 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -62,5 +62,3 @@
%td.coverage
- if generic_commit_status.try(:coverage)
#{generic_commit_status.coverage}%
-
- %td
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 409f4701e4b..0a66d60accc 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,7 +1,9 @@
- if subject.target_url
= link_to subject.target_url do
- = render_status_with_link('commit status', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
- else
- = render_status_with_link('commit status', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml
index 4c5dd9b88bf..1b0dbbb8111 100644
--- a/app/views/projects/group_links/index.html.haml
+++ b/app/views/projects/group_links/index.html.haml
@@ -16,7 +16,7 @@
= label_tag :link_group_access, "Max access level", class: "label-light"
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
- %span.caret
+ = icon('caret-down')
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-light'
.clearable-input
diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml
new file mode 100644
index 00000000000..af9a5b19060
--- /dev/null
+++ b/app/views/projects/group_links/update.js.haml
@@ -0,0 +1,3 @@
+:plain
+ var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}');
+ $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 509b01c548a..4825820c4d9 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -10,7 +10,7 @@
Issues
= nav_link(controller: :boards) do
- = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
+ = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
%span
Board
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 7cf1923456e..3a6fbbc7fbc 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues"
%h3.page-title
Edit Issue ##{@issue.iid}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 3fb4191c60e..09347ad5fff 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title "#{@issue.to_reference} #{@issue.title}", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
@@ -23,8 +23,8 @@
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- %span.caret
Options
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can?(current_user, :create_issue, @project)
@@ -55,12 +55,12 @@
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line, author: @issue.author
+ = markdown_field(@issue, :title)
- if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
.wiki
= preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"], author: @issue.author)
+ = markdown_field(@issue, :description)
%textarea.hidden.js-task-list-field
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 73c6f2a046c..71f7f354d72 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -5,7 +5,7 @@
.visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
%button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
Options
- %span.caret
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right
%ul
%li
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 6901ba13ab7..52b187e7e58 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,6 +1,9 @@
+- @no_container = true
- page_title "Edit", @label.name, "Labels"
+= render "projects/issues/head"
-%h3.page-title
- Edit Label
-%hr
-= render 'form'
+%div{ class: container_class }
+ %h3.page-title
+ Edit Label
+ %hr
+ = render 'form'
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 49ddf901619..a1bb66cfb6c 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,9 @@
+- @no_container = true
- page_title "New Label"
+= render "projects/issues/head"
-%h3.page-title
- New Label
-%hr
-= render 'form'
+%div{ class: container_class }
+ %h3.page-title
+ New Label
+ %hr
+ = render 'form'
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index de39964fca8..466ec1475d8 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -65,19 +65,6 @@
- if @merge_request.errors.any?
= form_errors(@merge_request)
- - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
- .light-well.append-bottom-default
- .center
- %h4
- There isn't anything to merge.
- %p.slead
- - if @merge_request.source_branch == @merge_request.target_branch
- You'll need to use different branch names to get a valid comparison.
- - else
- %span.label-branch #{@merge_request.source_branch}
- and
- %span.label-branch #{@merge_request.target_branch}
- are the same.
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
diff --git a/app/views/projects/merge_requests/_new_diffs.html.haml b/app/views/projects/merge_requests/_new_diffs.html.haml
new file mode 100644
index 00000000000..74367ab9b7b
--- /dev/null
+++ b/app/views/projects/merge_requests/_new_diffs.html.haml
@@ -0,0 +1 @@
+= render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 00bd4e143df..da6927879a4 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -18,34 +18,38 @@
= f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.merge-request-tabs.nav-links.no-top.no-bottom
- %li.commits-tab
- = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
- Commits
- %span.badge= @commits.size
- - if @pipeline
- %li.builds-tab.active
- = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
- Builds
- %span.badge= @statuses.size
- %li.diffs-tab.active
- = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
- Changes
- %span.badge= @diffs.real_size
+ - if @commits.empty?
+ .commits-empty
+ %h4
+ There are no commits yet.
+ = custom_icon ('illustration_no_commits')
+ - else
+ %ul.merge-request-tabs.nav-links.no-top.no-bottom
+ %li.commits-tab.active
+ = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
+ Commits
+ %span.badge= @commits.size
+ - if @pipeline
+ %li.builds-tab
+ = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
+ %li.diffs-tab
+ = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @merge_request.diff_size
- .tab-content
- #commits.commits.tab-pane
- = render "projects/merge_requests/show/commits"
- #diffs.diffs.tab-pane.active
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .alert.alert-danger
- %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
- %p To preserve performance the line changes are not shown.
- - else
- = render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false
- - if @pipeline
- #builds.builds.tab-pane
- = render "projects/merge_requests/show/builds"
+ .tab-content
+ #commits.commits.tab-pane.active
+ = render "projects/merge_requests/show/commits"
+ #diffs.diffs.tab-pane
+ - # This tab is always loaded via AJAX
+ - if @pipeline
+ #builds.builds.tab-pane
+ = render "projects/merge_requests/show/builds"
+
+ .mr-loading-status
+ = spinner
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -54,6 +58,6 @@
});
:javascript
var merge_request = new MergeRequest({
- action: "#{(@show_changes_tab ? 'diffs' : 'new')}",
- setUrl: false
+ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
+ buildsLoaded: "#{@pipeline ? 'true' : 'false'}"
});
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 9f34ca9ff4e..662463bc72b 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
+- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
@@ -22,7 +22,7 @@
%span.dropdown.inline.prepend-left-5
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
Download as
- %span.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
@@ -47,7 +47,7 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits_count.nonzero?
- %ul.merge-request-tabs.nav-links.no-top.no-bottom
+ %ul.merge-request-tabs.nav-links.no-top.no-bottom{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
Discussion
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 03159f123f3..7c3ac6652ee 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index ebf18f6ac85..ed23d06ee5e 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,13 +1,13 @@
.detail-page-description.content-block
%h2.title
- = markdown escape_once(@merge_request.title), pipeline: :single_line, author: @merge_request.author
+ = markdown_field(@merge_request, :title)
%div
- if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@merge_request.description, cache_key: [@merge_request, "description"], author: @merge_request.author)
+ = markdown_field(@merge_request, :description)
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index e35291dff7d..e7c5bca6a37 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -19,8 +19,8 @@
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- %span.caret
Options
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
%li{ class: merge_request_button_visibility(@merge_request, true) }
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 904452fcc4f..eab48b78cb3 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -9,7 +9,7 @@
latest version
- else
version #{version_index(@merge_request_diff)}
- %span.caret
+ = icon('caret-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-title
%span Version:
@@ -39,7 +39,7 @@
version #{version_index(@start_version)}
- else
#{@merge_request.target_branch}
- %span.caret
+ = icon('caret-down')
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-title
%span Compared with:
@@ -64,6 +64,16 @@
#{@merge_request.target_branch} (base)
.monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+ - if different_base?(@start_version, @merge_request_diff)
+ .content-block
+ = icon('info-circle')
+ Selected versions have different base commits.
+ Changes will include
+ = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ new commits
+ from
+ %code #{@merge_request.target_branch}
+
- unless @merge_request_diff.latest? && !@start_sha
.comments-disabled-notif.content-block
= icon('info-circle')
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index a92f0f9f481..a82c846baa7 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -44,20 +44,5 @@
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
-- @merge_request.environments.sort_by(&:name).each do |environment|
- - if can?(current_user, :read_environment, environment)
- .mr-widget-heading
- .ci_widget.ci-success
- = ci_icon_for_status("success")
- %span.hidden-sm
- Deployed to
- = succeed '.' do
- = link_to environment.name, environment_path(environment), class: 'environment'
- - external_url = environment.external_url
- - if external_url
- = link_to external_url, target: '_blank' do
- = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
- %span.close-env-container
- - if environment.closeable?
- = link_to [:play, @project.namespace.becomes(Namespace), @project, environment.close_action], method: :post, class: 'close-evn-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do
- = icon('stop-circle-o', text: 'Stop environment')
+.js-success-icon.hidden
+ = ci_icon_for_status('success')
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 6f5ee5f16c5..842b6df310d 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -35,3 +35,4 @@
Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
= succeed '.' do
!= markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
+ = mr_assign_issues_link
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index ea618263a4a..608fdf1c5f5 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -12,6 +12,7 @@
merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}",
ci_message: {
@@ -33,4 +34,4 @@
merge_request_widget.clearEventListeners();
}
- merge_request_widget = new MergeRequestWidget(opts);
+ merge_request_widget = new window.gl.MergeRequestWidget(opts);
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index bf2e76f0083..ce43ca3a286 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -12,7 +12,7 @@
Merge When Build Succeeds
- unless @project.only_allow_merge_if_build_succeeds?
= button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
- %span.caret
+ = icon('caret-down')
%span.sr-only
Select Merge Moment
%ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index be682226ab6..11f41e75e63 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,8 +1,12 @@
+- @no_container = true
- page_title "Edit", @milestone.title, "Milestones"
+= render "projects/issues/head"
-%h3.page-title
- Edit Milestone ##{@milestone.iid}
+%div{ class: container_class }
-%hr
+ %h3.page-title
+ Edit Milestone ##{@milestone.iid}
-= render "form"
+ %hr
+
+ = render "form"
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 7f372b41698..cda093ade81 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,8 +1,11 @@
+- @no_container = true
- page_title "New Milestone"
+= render "projects/issues/head"
-%h3.page-title
- New Milestone
+%div{ class: container_class }
+ %h3.page-title
+ New Milestone
-%hr
+ %hr
-= render "form"
+ = render "form"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 73772cc0e32..c83818e9199 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,49 +1,52 @@
+- @no_container = true
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
+= render "projects/issues/head"
-.detail-page-header
- .status-box{ class: status_box_class(@milestone) }
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Past due
- - else
- Open
- %span.identifier
- Milestone ##{@milestone.iid}
- - if @milestone.expires_at
- %span.creator
- &middot;
- = @milestone.expires_at
- .pull-right
- - if can?(current_user, :admin_milestone, @project)
- - if @milestone.active?
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
+%div{ class: container_class }
+ .detail-page-header
+ .status-box{ class: status_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Past due
- else
- = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
+ Open
+ %span.identifier
+ Milestone ##{@milestone.iid}
+ - if @milestone.expires_at
+ %span.creator
+ &middot;
+ = @milestone.expires_at
+ .pull-right
+ - if can?(current_user, :admin_milestone, @project)
+ - if @milestone.active?
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
+ - else
+ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
- Edit
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
+ Edit
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
- Delete
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+ Delete
-.detail-page-description.milestone-detail
- %h2.title
- = markdown escape_once(@milestone.title), pipeline: :single_line
- %div
- - if @milestone.description.present?
- .description
- .wiki
- = preserve do
- = markdown @milestone.description
+ .detail-page-description.milestone-detail
+ %h2.title
+ = markdown_field(@milestone, :title)
+ %div
+ - if @milestone.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown_field(@milestone, :description)
-- if @milestone.total_items_count(current_user).zero?
- .alert.alert-success.prepend-top-default
- %span Assign some issues to this milestone.
-- elsif @milestone.complete?(current_user) && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. You may close this milestone now.
+ - if @milestone.total_items_count(current_user).zero?
+ .alert.alert-success.prepend-top-default
+ %span Assign some issues to this milestone.
+ - elsif @milestone.complete?(current_user) && @milestone.active?
+ .alert.alert-success.prepend-top-default
+ %span All issues for this milestone are closed. You may close this milestone now.
-= render 'shared/milestones/summary', milestone: @milestone, project: @project
-= render 'shared/milestones/tabs', milestone: @milestone
+ = render 'shared/milestones/summary', milestone: @milestone, project: @project
+ = render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index b2ece44d966..29df1bab04e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -8,7 +8,7 @@
.project-network
.controls
= form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
- = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success' do
= icon('search')
.inline.prepend-left-20
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 788be4a0047..73fe6a715fa 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -61,7 +61,7 @@
.note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text.md
= preserve do
- = note.note_html
+ = note.redacted_note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
= render 'projects/notes/edit_form', note: note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 8352eba7446..00b62a595ff 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -14,7 +14,7 @@
.disabled-comment.text-center
.disabled-comment-text.inline
Please
- = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes')
+ = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
= link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index 7d421c0e740..b10dd47709f 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -1,7 +1,7 @@
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
+ .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) }
%ul{ class: (container_class) }
- if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 5800ef7de48..d288efc546f 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -33,7 +33,7 @@
- if @commit
.commit-box.content-block
%h3.commit-title
- = markdown escape_once(@commit.title), pipeline: :single_line
+ = markdown(@commit.title, pipeline: :single_line)
- if @commit.description.present?
%pre.commit-description
- = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
+ = preserve(markdown(@commit.description, pipeline: :single_line))
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 2d1df095bfa..9eeef5f57b4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -43,7 +43,7 @@
.nothing-here-block No pipelines to show
- else
.table-holder
- %table.table.builds
+ %table.table.ci-table
%thead
%th.col-xs-1.col-sm-1 Status
%th.col-xs-2.col-sm-4 Pipeline
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 75943c64276..688535ad764 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,8 +1,11 @@
+- @no_container = true
- page_title "Pipeline"
+= render "projects/pipelines/head"
-.prepend-top-default
- - if @commit
- = render "projects/pipelines/info"
- %div.block-connector
+%div{ class: container_class }
+ .prepend-top-default
+ - if @commit
+ = render "projects/pipelines/info"
+ %div.block-connector
-= render "projects/commit/pipeline", pipeline: @pipeline
+ = render "projects/commit/pipeline", pipeline: @pipeline
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index e783d8c72c5..9738f369a35 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -1,7 +1,7 @@
.panel.panel-default
.panel-heading
+ Group members with access to
%strong #{@group.name}
- group members
%span.badge= members.size
- if can?(current_user, :admin_group_member, @group)
.controls
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
new file mode 100644
index 00000000000..d7f5fa96527
--- /dev/null
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -0,0 +1,7 @@
+.panel.panel-default.project-members-groups
+ .panel-heading
+ Groups with access to
+ %strong #{@project.name}
+ %span.badge= group_links.size
+ %ul.content-list
+ = render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index fa8cbf71733..79dcd7a6ee9 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,27 +1,22 @@
-= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
- .form-group
- = f.label :user_ids, "People", class: 'control-label'
- .col-sm-10
- = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
- .help-block
+= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
+ .row
+ .col-md-4.col-lg-6
+ = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true)
+ .help-block.append-bottom-10
Search for users by name, username, or email, or invite new ones using their email address.
- .form-group
- = f.label :access_level, "Project Access", class: 'control-label'
- .col-sm-10
- = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2"
- .help-block
- Read more about role permissions
- %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
+ .col-md-3.col-lg-2
+ = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
+ .help-block.append-bottom-10
+ = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ about role permissions
- .form-group
- = f.label :expires_at, 'Access expiration date', class: 'control-label'
- .col-sm-10
+ .col-md-3.col-lg-2
.clearable-input
- = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
+ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- .help-block
+ .help-block.append-bottom-10
On this date, the user(s) will automatically lose access to this project.
- .form-actions
- = f.submit 'Add users to project', class: "btn btn-create"
+ .col-md-2
+ = f.submit "Add to project", class: "btn btn-create btn-block"
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index b0bfdd235f7..c1e894d8f40 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,19 +1,7 @@
.panel.panel-default
.panel-heading
+ Users with access to
%strong #{@project.name}
- project members
- %span.badge= members.size
- .controls
- = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
- = button_tag class: 'btn', title: 'Search' do
- = icon("search")
+ %span.badge= @project_members.total_count
%ul.content-list
= render partial: 'shared/members/member', collection: members, as: :member
-
-:javascript
- $('form.member-search-form').on('submit', function (event) {
- event.preventDefault();
- Turbolinks.visit(this.action + '?' + $(this).serialize());
- });
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9d063b3081f..bdeb704b6da 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,24 +1,28 @@
- page_title "Members"
-.project-members-page.js-project-members-page.prepend-top-default
+.project-members-page.prepend-top-default
+ %h4.project-members-title.clearfix
+ Members
+ = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project"
- if can?(current_user, :admin_project_member, @project)
- .panel.panel-default
- .panel-heading
- Add new user to project
- .controls
- = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
- .panel-body
- %p.light
- Users with access to this project are listed below.
- = render "new_project_member"
+ .project-members-new.append-bottom-default
+ %p.clearfix
+ Add new user to
+ %strong= @project.name
+ = render "new_project_member"
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters
- = render 'team', members: @project_members
-
- - if @group
- = render "group_members", members: @group_members
+ .append-bottom-default.clearfix
+ %h5.member.existing-title
+ Existing users and groups
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-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" }
+ = icon("search")
+ - if @group_links.any?
+ = render 'groups', group_links: @group_links
- - if @project_group_links.any? && @project.allowed_to_share_with_group?
- = render "shared_group_members"
+ = render 'team', members: @project_members
+ = paginate @project_members, theme: "gitlab"
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
index 37e55dc72a3..91927181efb 100644
--- a/app/views/projects/project_members/update.js.haml
+++ b/app/views/projects/project_members/update.js.haml
@@ -1,3 +1,3 @@
:plain
- $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
- new gl.MemberExpirationDate();
+ var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}');
+ $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index 43a6fdfd103..d9c39fb87b7 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
- = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line, author: commit.author
+ = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author)
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 752b9e060d5..5afa193357e 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -1,8 +1,8 @@
%h3 Shared Runners
.bs-callout.bs-callout-warning.shared-runners-description
- - if shared_runners_text.present?
- = markdown(shared_runners_text, pipeline: 'plain_markdown')
+ - if current_application_settings.shared_runners_text.present?
+ = markdown_field(current_application_settings, :shared_runners_text)
- else
GitLab Shared Runners execute code of different projects on the same Runner
unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 9773b8438ec..32e1f8a21b0 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -12,7 +12,7 @@
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
- %span.caret
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
- if can?(current_user, :create_project_snippet, @project)
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index a156d98bab8..05fccb4f976 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -30,4 +30,4 @@
.description.prepend-top-default
.wiki
= preserve do
- = markdown release.description
+ = markdown_field(release, :description)
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 6adbe9351dc..7a0d9dcc94f 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -14,7 +14,7 @@
%button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
%span.light
= @sort.humanize
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_tags_path(sort: nil) do
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 4dd7439b2d0..155af755759 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -33,6 +33,6 @@
.description
.wiki
= preserve do
- = markdown @release.description
+ = markdown_field(@release, :description)
- else
This tag has no release notes.
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 8f68d6d1b87..e010f21de5a 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -7,7 +7,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project, author: issue.author }))
+ = search_md_sanitize(issue, :description)
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 6331c2bd6b0..07b17bc69c0 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -6,7 +6,7 @@
- if merge_request.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project, author: merge_request.author }))
+ = search_md_sanitize(merge_request, :description)
%span.light
#{merge_request.project.name_with_namespace}
.pull-right
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index b31595d8d1c..9664f65a36e 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -6,4 +6,4 @@
- if milestone.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(milestone.description))
+ = search_md_sanitize(milestone, :description)
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index e0400083870..f3701b89bb4 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -23,4 +23,4 @@
.note-search-result
.term
= preserve do
- = search_md_sanitize(markdown(note.note, {no_header_anchors: true, author: note.author}))
+ = search_md_sanitize(note, :note)
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 8824bcc158e..3480800369a 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,4 +1,5 @@
%ul.nav-links.event-filter.scrolling-tabs
+ = event_filter_link EventFilter.all, 'All'
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 77676454b57..6f593e8dff9 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -12,4 +12,4 @@
= link_to_label(label, tooltip: false)
- if label.description
%span.label-description
- = markdown(label.description, pipeline: :single_line)
+ = markdown_field(label, :description)
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index 51622931e24..fbbf6f358c5 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -3,7 +3,7 @@
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }
%a.btn.btn-new.new-project-item-select-button
= local_assigns[:label]
- %b.caret
+ = icon('caret-down')
:javascript
$('.new-project-item-select-button').on('click', function() {
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 36bbac6fbf5..68e05cb72e1 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -5,7 +5,7 @@
= sort_options_hash[@sort]
- else
= sort_title_recently_created
- %b.caret
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to page_filter_path(sort: sort_value_priority, label: true) do
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 1ad95351005..dc4ee3074d2 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -35,4 +35,4 @@
- if group.description.present?
.description
- = markdown(group.description, pipeline: :description)
+ = markdown_field(group, :description)
diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg
new file mode 100644
index 00000000000..4f9d9add60d
--- /dev/null
+++ b/app/views/shared/icons/_illustration_no_commits.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="m4.01 2h1.102c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-1.102c-2.218 0-4.01 1.788-4.01 4 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-1.108.892-2 2.01-2m12.702 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m8.088 0c.822 0 1.554.503 1.86 1.254.208.512.791.758 1.303.55.512-.208.758-.791.55-1.303-.609-1.497-2.069-2.5-3.712-2.5h-2.188c-.552 0-1 .448-1 1 0 .552.448 1 1 1h2.188m2.01 12.518c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72c-.552 0-1 .448-1 1 0 .552.448 1 1 1h.72c2.218 0 4.01-1.788 4.01-4v-.382c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.382m-14.325 2c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-8.47 0c-.755 0-1.438-.424-1.782-1.085-.255-.49-.859-.681-1.349-.426-.49.255-.681.859-.426 1.349.684 1.316 2.046 2.162 3.556 2.162h2.57c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-2.57m-2.01-12.136c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-6.664c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.764c0 .552.448 1 1 1 .552 0 1-.448 1-1v-.764" id="0"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="1"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="2"/><path d="m131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9c0-.552-.447-.999-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01c1.655 0 2.996 1.344 2.996 2.999v9c0 1.657-1.35 2.999-2.996 2.999h-22.01c-1.655 0-2.996-1.344-2.996-2.999v-9c0-1.657 1.35-2.999 2.996-2.999" id="3"/><g transform="translate(0 59)"><use xlink:href="#0"/><circle cx="21" cy="24" r="10"/><use xlink:href="#1"/><use xlink:href="#2"/><use xlink:href="#3"/></g></g></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index c3f4e10c954..a7944a60130 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -23,6 +23,8 @@
data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
%ul.dropdown-footer-list
%li
+ %a.no-template
+ No template
%a.reset-template
Reset template
%div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' }
@@ -85,20 +87,20 @@
.issuable-form-select-holder
- if issuable.assignee_id
= f.hidden_field :assignee_id
- = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+ = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } })
.form-group.issue-milestone
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input"
+ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group
- has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
= f.hidden_field :label_ids, multiple: true, value: ''
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
.issuable-form-select-holder
- = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }
+ = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
- if has_due_date
.col-lg-6
.form-group
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 6d307611640..22b5a6aa11b 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -8,6 +8,7 @@
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
- selected_toggle = local_assigns.fetch(:selected_toggle, nil)
+- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options)
- classes << 'js-extra-options' if extra_options
@@ -23,7 +24,7 @@
= multi_label_name(selected, "Labels")
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create }
+ = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index ab3cc33d18f..f27a9002ec2 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -2,9 +2,10 @@
- extra_class = extra_class || ''
- show_menu_above = show_menu_above || false
- selected_text = selected.try(:title)
+- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
- if selected.present?
= hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id)
-= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
+= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project
%ul.dropdown-footer-list
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
new file mode 100644
index 00000000000..1c0346bbc78
--- /dev/null
+++ b/app/views/shared/members/_group.html.haml
@@ -0,0 +1,29 @@
+- group_link = local_assigns[:group_link]
+- group = group_link.group
+- can_admin_member = can?(current_user, :admin_project_member, @project)
+%li.member.group_member{ id: "group_member_#{group_link.id}" }
+ %span{ class: "list-item-name" }
+ = image_tag group_icon(group), class: "avatar s40", alt: ''
+ %strong
+ = link_to group.name, group_path(group)
+ .cgray
+ Joined #{time_ago_with_tooltip(group.created_at)}
+ - if group_link.expires?
+ ·
+ %span{ class: ('text-warning' if group_link.expires_soon?) }
+ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
+ .controls.member-controls
+ = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
+ = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member
+ .prepend-left-5.clearable-input.member-form-control
+ = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
+ %i.clear-icon.js-clear-input
+ - if can_admin_member
+ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
+ remote: true,
+ method: :delete,
+ data: { confirm: "Are you sure you want to remove #{group.name}?" },
+ class: 'btn btn-remove prepend-left-10' do
+ %span.visible-xs-block
+ Delete
+ = icon('trash', class: 'hidden-xs')
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 5f20e4bd42a..432047a1c4e 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,59 +1,29 @@
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
-- user = member.user
+- user = local_assigns.fetch(:user, member.user)
+- source = member.source
+- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
-%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) }
- - if show_roles
- .controls
- %strong.control-text= member.human_access
- - if show_controls
- - if !user && can?(current_user, action_member_permission(:admin, member), member.source)
- = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
- method: :post,
- class: 'btn'
-
- - if can?(current_user, action_member_permission(:update, member), member)
- = button_tag icon('pencil'),
- type: 'button',
- class: 'btn inline js-toggle-button',
- title: 'Edit'
-
- - if member.request?
- = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
- method: :post,
- class: 'btn btn-success',
- title: 'Grant access'
-
- - if can?(current_user, action_member_permission(:destroy, member), member)
- - if current_user == user
- = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
- method: :delete,
- data: { confirm: leave_confirmation_message(member.source) },
- class: 'btn btn-remove'
- - else
- = link_to icon('trash'), member,
- remote: true,
- method: :delete,
- data: { confirm: remove_member_message(member) },
- class: 'btn btn-remove',
- title: remove_member_title(member)
-
-
- %span{ class: ("list-item-name" if show_controls) }
+%li.member{ class: dom_class(member), id: dom_id(member) }
+ %span.list-item-name
- if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
%strong
= link_to user.name, user_path(user)
- %span.cgray= user.username
+ %span.cgray= user.to_reference
- if user == current_user
- %span.label.label-success It's you
+ %span.label.label-success.prepend-left-5 It's you
- if user.blocked?
%label.label.label-danger
%strong Blocked
- .cgray
+ - if source.instance_of?(Group) && !@group
+ = link_to source, class: "member-group-link prepend-left-5" do
+ = "· #{source.name}"
+
+ .hidden-xs.cgray
- if member.request?
Requested
= time_ago_with_tooltip(member.requested_at)
@@ -73,20 +43,44 @@
by
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
-
- if show_roles
- .edit-member.hide.js-toggle-content
- %br
- = form_for member, remote: true, html: { class: 'form-horizontal' } do |f|
- .form-group
- = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label'
- .col-sm-10
- = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}"
- .form-group
- = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label'
- .col-sm-10
- .clearable-input
- = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}"
+ .controls.member-controls
+ - if show_controls
+ - if user != current_user
+ = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
+ = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_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
%i.clear-icon.js-clear-input
- .prepend-top-10
- = f.submit 'Save', class: 'btn btn-save btn-sm'
+ - 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'
+
+ - elsif member.request? && can_admin_member
+ = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
+ method: :post,
+ class: 'btn btn-success prepend-left-10',
+ title: 'Grant access'
+
+ - if can?(current_user, action_member_permission(:destroy, member), member)
+ - if current_user == user
+ = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
+ method: :delete,
+ data: { confirm: leave_confirmation_message(member.source) },
+ class: 'btn btn-remove prepend-left-10'
+ - else
+ = link_to member,
+ remote: true,
+ method: :delete,
+ data: { confirm: remove_member_message(member) },
+ class: 'btn btn-remove prepend-left-10',
+ title: remove_member_title(member) do
+ %span.visible-xs-block
+ Delete
+ = icon('trash', class: 'hidden-xs')
+ - else
+ %span.member-access-text= member.human_access
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 40b39e850b0..10050adfda5 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,8 +1,8 @@
- if requesters.any?
.panel.panel-default
.panel-heading
+ Users requesting access to
%strong= membership_source.name
- access requests
%span.badge= requesters.size
%ul.content-list
= render partial: 'shared/members/member', collection: requesters, as: :member
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index b15e8ea73fe..33f93dccd3c 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -8,7 +8,7 @@
= link_to milestones_label_path(options) do
- render_colored_label(label, tooltip: false)
%span.prepend-description-left
- = markdown(label.description, pipeline: :single_line)
+ = markdown_field(label, :description)
.pull-info-right
%span.append-right-20
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 7ff947a51db..548215243db 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -26,7 +26,7 @@
.detail-page-description.milestone-detail
%h2.title
- = markdown escape_once(milestone.title), pipeline: :single_line
+ = markdown_field(milestone, :title)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
@@ -55,4 +55,3 @@
Open
%td
= ms.expires_at
-
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index ff1cf966a9b..feaa5570c21 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -11,7 +11,7 @@
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } }
- %span.caret
+ = icon('caret-down')
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.notifications-btn#notifications-button{ type: "button", data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } }
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 66c309644a7..e8668048703 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -50,4 +50,4 @@
class: "commit-row-message"
- elsif project.description.present?
.description
- = markdown(project.description, pipeline: :description)
+ = markdown_field(project, :description)
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 773ce8ac240..dcdba01aee9 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -1,9 +1,12 @@
- unless @snippet.content.empty?
- if markup?(@snippet.file_name)
%textarea.markdown-snippet-copy.blob-content{data: {blob_id: @snippet.id}}
- = @snippet.data
+ = @snippet.content
.file-content.wiki
- = render_markup(@snippet.file_name, @snippet.data)
+ - if gitlab_markdown?(@snippet.file_name)
+ = preserve(markdown_field(@snippet, :content))
+ - else
+ = render_markup(@snippet.file_name, @snippet.content)
- else
= render 'shared/file_highlight', blob: @snippet
- else
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 7ae4211ddfd..d7506e07ff6 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -21,4 +21,4 @@
= render "snippets/actions"
%h2.snippet-title.prepend-top-0.append-bottom-0
- = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
+ = markdown_field(@snippet, :title)
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index c446dc3bdc1..1d0e549ed3d 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -12,7 +12,7 @@
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
- %span.caret
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
%li
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index cd89155c616..27d7a6c5bb6 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -9,6 +9,7 @@
.file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+ = link_to 'Download', download_snippet_path(@snippet), class: "btn btn-sm"
= render 'shared/snippets/blob'
= render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file
diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml
index 9657101ace5..232ca26c1af 100644
--- a/app/views/u2f/_authenticate.html.haml
+++ b/app/views/u2f/_authenticate.html.haml
@@ -6,7 +6,7 @@
%script#js-authenticate-u2f-setup{ type: "text/template" }
%div
%p Insert your security key (if you haven't already), and press the button below.
- %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Login Via U2F Device
+ %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Sign in via U2F device
%script#js-authenticate-u2f-in-progress{ type: "text/template" }
%p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 60fc0c0daf6..1e0752bd3c3 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -40,11 +40,11 @@
.user-info
.cover-title
= @user.name
- %span.handle
- @#{@user.username}
.cover-desc.member-date
%span.middle-dot-divider
+ @#{@user.username}
+ %span.middle-dot-divider
Member since #{@user.created_at.to_s(:medium)}
.cover-desc
@@ -82,7 +82,7 @@
%ul.nav-links.center.user-profile-nav
%li.js-activity-tab
- = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
+ = link_to user_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
Activity
%li.js-groups-tab
= link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do
diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb
new file mode 100644
index 00000000000..0680645a8db
--- /dev/null
+++ b/app/workers/build_coverage_worker.rb
@@ -0,0 +1,9 @@
+class BuildCoverageWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id)
+ .try(:update_coverage)
+ end
+end
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
new file mode 100644
index 00000000000..e7286b77ac5
--- /dev/null
+++ b/app/workers/build_finished_worker.rb
@@ -0,0 +1,10 @@
+class BuildFinishedWorker
+ include Sidekiq::Worker
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id).try do |build|
+ BuildCoverageWorker.new.perform(build.id)
+ BuildHooksWorker.new.perform(build.id)
+ end
+ end
+end
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
new file mode 100644
index 00000000000..e22ececb3fd
--- /dev/null
+++ b/app/workers/build_hooks_worker.rb
@@ -0,0 +1,9 @@
+class BuildHooksWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id)
+ .try(:execute_hooks)
+ end
+end
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
new file mode 100644
index 00000000000..500d357ce31
--- /dev/null
+++ b/app/workers/build_success_worker.rb
@@ -0,0 +1,27 @@
+class BuildSuccessWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id).try do |build|
+ create_deployment(build)
+ end
+ end
+
+ private
+
+ def create_deployment(build)
+ return if build.environment.blank?
+
+ service = CreateDeploymentService.new(
+ build.project, build.user,
+ environment: build.environment,
+ sha: build.sha,
+ ref: build.ref,
+ tag: build.tag,
+ options: build.options.to_h[:environment],
+ variables: build.variables)
+
+ service.execute(build)
+ end
+end
diff --git a/app/workers/clear_database_cache_worker.rb b/app/workers/clear_database_cache_worker.rb
new file mode 100644
index 00000000000..c541daba50e
--- /dev/null
+++ b/app/workers/clear_database_cache_worker.rb
@@ -0,0 +1,23 @@
+# This worker clears all cache fields in the database, working in batches.
+class ClearDatabaseCacheWorker
+ include Sidekiq::Worker
+
+ BATCH_SIZE = 1000
+
+ def perform
+ CacheMarkdownField.caching_classes.each do |kls|
+ fields = kls.cached_markdown_fields.html_fields
+ clear_cache_fields = fields.each_with_object({}) do |field, memo|
+ memo[field] = nil
+ end
+
+ Rails.logger.debug("Clearing Markdown cache for #{kls}: #{fields.inspect}")
+
+ kls.unscoped.in_batches(of: BATCH_SIZE) do |relation|
+ relation.update_all(clear_cache_fields)
+ end
+ end
+
+ nil
+ end
+end
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index c64ea108d52..174eabff9fd 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -2,12 +2,11 @@ class ExpireBuildArtifactsWorker
include Sidekiq::Worker
def perform
- Rails.logger.info 'Cleaning old build artifacts'
+ Rails.logger.info 'Scheduling removal of build artifacts'
- builds = Ci::Build.with_expired_artifacts
- builds.find_each(batch_size: 50).each do |build|
- Rails.logger.debug "Removing artifacts build #{build.id}..."
- build.erase_artifacts!
- end
+ build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
+ build_ids = build_ids.map { |build_id| [build_id] }
+
+ Sidekiq::Client.push_bulk('class' => ExpireBuildInstanceArtifactsWorker, 'args' => build_ids )
end
end
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
new file mode 100644
index 00000000000..916c2e633c1
--- /dev/null
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -0,0 +1,11 @@
+class ExpireBuildInstanceArtifactsWorker
+ include Sidekiq::Worker
+
+ def perform(build_id)
+ build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id)
+ return unless build
+
+ Rails.logger.info "Removing artifacts build #{build.id}..."
+ build.erase_artifacts!
+ end
+end
diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb
new file mode 100644
index 00000000000..ab5e9f6daad
--- /dev/null
+++ b/app/workers/pipeline_hooks_worker.rb
@@ -0,0 +1,9 @@
+class PipelineHooksWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(pipeline_id)
+ Ci::Pipeline.find_by(id: pipeline_id)
+ .try(:execute_hooks)
+ end
+end
diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/pipeline_process_worker.rb
index 26ea5f1c24d..f44227d7086 100644
--- a/app/workers/process_pipeline_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -1,4 +1,4 @@
-class ProcessPipelineWorker
+class PipelineProcessWorker
include Sidekiq::Worker
sidekiq_options queue: :default
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
new file mode 100644
index 00000000000..5dd443fea59
--- /dev/null
+++ b/app/workers/pipeline_success_worker.rb
@@ -0,0 +1,12 @@
+class PipelineSuccessWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(pipeline_id)
+ Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
+ MergeRequests::MergeWhenBuildSucceedsService
+ .new(pipeline.project, nil)
+ .trigger(pipeline)
+ end
+ end
+end
diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/pipeline_update_worker.rb
index 6ef5678073e..44a7f24e401 100644
--- a/app/workers/update_pipeline_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -1,4 +1,4 @@
-class UpdatePipelineWorker
+class PipelineUpdateWorker
include Sidekiq::Worker
sidekiq_options queue: :default
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
new file mode 100644
index 00000000000..df4c4a6628b
--- /dev/null
+++ b/app/workers/trending_projects_worker.rb
@@ -0,0 +1,11 @@
+class TrendingProjectsWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :trending_projects
+
+ def perform
+ Rails.logger.info('Refreshing trending projects')
+
+ TrendingProject.refresh!
+ end
+end
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
new file mode 100644
index 00000000000..03f0528cdae
--- /dev/null
+++ b/app/workers/update_merge_requests_worker.rb
@@ -0,0 +1,16 @@
+class UpdateMergeRequestsWorker
+ include Sidekiq::Worker
+
+ def perform(project_id, user_id, oldrev, newrev, ref)
+ project = Project.find_by(id: project_id)
+ return unless project
+
+ user = User.find_by(id: user_id)
+ return unless user
+
+ MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref)
+
+ push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, [])
+ SystemHooksService.new.execute_hooks(push_data, :push_hooks)
+ end
+end