diff options
author | Phil Hughes <me@iamphill.com> | 2019-05-29 10:24:35 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2019-05-29 10:24:35 +0100 |
commit | 1224aa516eefe7be1c8ca306625d5d0b6d524d76 (patch) | |
tree | 0fe19052d0496ecef64a1f0727a3dc97a85c4d1e | |
parent | 4a9387242dbbfee9e9ddc7b46eb69ad6a2f4ba2c (diff) | |
download | gitlab-ce-1224aa516eefe7be1c8ca306625d5d0b6d524d76.tar.gz |
Created repository list breadcrumbs Vue app
-rw-r--r-- | app/assets/javascripts/repository/components/breadcrumbs.vue | 61 | ||||
-rw-r--r-- | app/assets/javascripts/repository/index.js | 25 | ||||
-rw-r--r-- | app/assets/javascripts/repository/queries/getProjectShortPath.graphql | 3 | ||||
-rw-r--r-- | app/assets/javascripts/repository/utils/title.js | 2 | ||||
-rw-r--r-- | app/helpers/projects_helper.rb | 4 | ||||
-rw-r--r-- | app/views/projects/_files.html.haml | 7 | ||||
-rw-r--r-- | app/views/projects/_home_panel.html.haml | 2 | ||||
-rw-r--r-- | app/views/projects/tree/_readme.html.haml | 2 | ||||
-rw-r--r-- | app/views/projects/tree/_tree_header.html.haml | 121 | ||||
-rw-r--r-- | locale/gitlab.pot | 3 | ||||
-rw-r--r-- | spec/features/projects/files/project_owner_creates_license_file_spec.rb | 1 | ||||
-rw-r--r-- | spec/features/projects/files/user_creates_files_spec.rb | 1 | ||||
-rw-r--r-- | spec/features/projects/show/user_sees_collaboration_links_spec.rb | 1 | ||||
-rw-r--r-- | spec/frontend/repository/components/breadcrumbs_spec.js | 44 |
14 files changed, 208 insertions, 69 deletions
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue new file mode 100644 index 00000000000..6eca015036f --- /dev/null +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -0,0 +1,61 @@ +<script> +import getRefMixin from '../mixins/get_ref'; +import getProjectShortPath from '../queries/getProjectShortPath.graphql'; + +export default { + apollo: { + projectShortPath: { + query: getProjectShortPath, + }, + }, + mixins: [getRefMixin], + props: { + currentPath: { + type: String, + required: false, + default: '/', + }, + }, + data() { + return { + projectShortPath: '', + }; + }, + computed: { + pathLinks() { + return this.currentPath + .split('/') + .filter(p => p !== '') + .reduce( + (acc, name, i) => { + const path = `${i > 0 ? acc[i].path : ''}/${name}`; + + return acc.concat({ + name, + path, + to: `/tree/${this.ref}${path}`, + }); + }, + [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}` }], + ); + }, + }, + methods: { + isLast(i) { + return i === this.pathLinks.length - 1; + }, + }, +}; +</script> + +<template> + <nav :aria-label="__('Files breadcrumb')"> + <ol class="breadcrumb repo-breadcrumb"> + <li v-for="(link, i) in pathLinks" :key="i" class="breadcrumb-item"> + <router-link :to="link.to" :aria-current="isLast(i) ? 'page' : null"> + {{ link.name }} + </router-link> + </li> + </ol> + </nav> +</template> diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index f992d4b6d54..52f53be045b 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -1,24 +1,27 @@ import Vue from 'vue'; import createRouter from './router'; import App from './components/app.vue'; +import Breadcrumbs from './components/breadcrumbs.vue'; import apolloProvider from './graphql'; import { setTitle } from './utils/title'; export default function setupVueRepositoryList() { const el = document.getElementById('js-tree-list'); - const { projectPath, ref, fullName } = el.dataset; + const { projectPath, projectShortPath, ref, fullName } = el.dataset; const router = createRouter(projectPath, ref); apolloProvider.clients.defaultClient.cache.writeData({ data: { projectPath, + projectShortPath, ref, }, }); - router.afterEach(({ params: { pathMatch } }) => setTitle(pathMatch, ref, fullName)); - router.afterEach(to => { - const isRoot = to.params.pathMatch === undefined || to.params.pathMatch === '/'; + router.afterEach(({ params: { pathMatch } }) => { + const isRoot = pathMatch === undefined || pathMatch === '/'; + + setTitle(pathMatch, ref, fullName); if (!isRoot) { document @@ -31,6 +34,20 @@ export default function setupVueRepositoryList() { .forEach(elem => elem.classList.toggle('hidden', !isRoot)); }); + // eslint-disable-next-line no-new + new Vue({ + el: document.getElementById('js-repo-breadcrumb'), + router, + apolloProvider, + render(h) { + return h(Breadcrumbs, { + props: { + currentPath: this.$route.params.pathMatch, + }, + }); + }, + }); + return new Vue({ el, router, diff --git a/app/assets/javascripts/repository/queries/getProjectShortPath.graphql b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql new file mode 100644 index 00000000000..34eb26598c2 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql @@ -0,0 +1,3 @@ +query getProjectShortPath { + projectShortPath @client +} diff --git a/app/assets/javascripts/repository/utils/title.js b/app/assets/javascripts/repository/utils/title.js index 3c3e918c0a8..4e194640e92 100644 --- a/app/assets/javascripts/repository/utils/title.js +++ b/app/assets/javascripts/repository/utils/title.js @@ -1,5 +1,7 @@ // eslint-disable-next-line import/prefer-default-export export const setTitle = (pathMatch, ref, project) => { + if (!pathMatch) return; + const path = pathMatch.replace(/^\//, ''); const isEmpty = path === ''; diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f798bfbf703..4cf1d6e93a5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -655,4 +655,8 @@ module ProjectsHelper project.builds_enabled? && !project.repository.gitlab_ci_yml end + + def vue_file_list_enabled? + Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project) + end end diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 7f50a7e4294..2b0c3985755 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -4,7 +4,6 @@ - project = local_assigns.fetch(:project) { @project } - content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) } - show_auto_devops_callout = show_auto_devops_callout?(@project) -- vue_file_list = Feature.enabled?(:vue_file_list, @project) #tree-holder.tree-holder.clearfix .nav-block @@ -14,11 +13,11 @@ = render 'shared/commit_well', commit: commit, ref: ref, project: project - if is_project_overview - .project-buttons.append-bottom-default{ class: ("js-keep-hidden-on-navigation" if vue_file_list) } + .project-buttons.append-bottom-default{ class: ("js-keep-hidden-on-navigation" if vue_file_list_enabled?) } = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) - - if vue_file_list - #js-tree-list{ data: { project_path: @project.full_path, full_name: @project.name_with_namespace, ref: ref } } + - if vue_file_list_enabled? + #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } } - if @tree.readme = render "projects/tree/readme", readme: @tree.readme - else diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 6de8848d3a1..9f5241344a7 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,7 +1,7 @@ - empty_repo = @project.empty_repo? - show_auto_devops_callout = show_auto_devops_callout?(@project) - max_project_topic_length = 15 -.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if Feature.enabled?(:vue_file_list, @project))] } +.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] } .row.append-bottom-8 .home-panel-title-row.col-md-12.col-lg-6.d-flex .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index e935af23659..4f6c7e1f9a6 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,5 +1,5 @@ - if readme.rich_viewer - %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if Feature.enabled?(:vue_file_list, @project))] } + %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if vue_file_list_enabled?)] } .js-file-title.file-title = blob_icon readme.mode, readme.name = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index ec8e5234bd4..ea6349f2f57 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -6,71 +6,74 @@ = render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true - if on_top_of_branch? - - addtotree_toggle_attributes = { href: '#', 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' } + - addtotree_toggle_attributes = { 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' } - else - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' } - %ul.breadcrumb.repo-breadcrumb - %li.breadcrumb-item - = link_to project_tree_path(@project, @ref) do - = @project.path - - path_breadcrumbs do |title, path| + - if vue_file_list_enabled? + #js-repo-breadcrumb + - else + %ul.breadcrumb.repo-breadcrumb %li.breadcrumb-item - = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) + = link_to project_tree_path(@project, @ref) do + = @project.path + - path_breadcrumbs do |title, path| + %li.breadcrumb-item + = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) - - if can_collaborate || can_create_mr_from_fork - %li.breadcrumb-item - %a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes } - = sprite_icon('plus', size: 16, css_class: 'float-left') - = sprite_icon('arrow-down', size: 16, css_class: 'float-left') - - if on_top_of_branch? - .add-to-tree-dropdown - %ul.dropdown-menu - - if can_edit_tree? - %li.dropdown-header - #{ _('This directory') } - %li - = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do - #{ _('New file') } - %li - = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do - #{ _('Upload file') } - %li - = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do - #{ _('New directory') } - - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) - %li - - continue_params = { to: project_new_blob_path(@project, @id), - notice: edit_in_new_fork_notice, - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - = link_to fork_path, method: :post do - #{ _('New file') } - %li - - continue_params = { to: request.fullpath, - notice: edit_in_new_fork_notice + " Try to upload a file again.", - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - = link_to fork_path, method: :post do - #{ _('Upload file') } - %li - - continue_params = { to: request.fullpath, - notice: edit_in_new_fork_notice + " Try to create a new directory again.", - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - = link_to fork_path, method: :post do - #{ _('New directory') } + - if can_collaborate || can_create_mr_from_fork + %li.breadcrumb-item + %button.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes, type: 'button' } + = sprite_icon('plus', size: 16, css_class: 'float-left') + = sprite_icon('arrow-down', size: 16, css_class: 'float-left') + - if on_top_of_branch? + .add-to-tree-dropdown + %ul.dropdown-menu + - if can_edit_tree? + %li.dropdown-header + #{ _('This directory') } + %li + = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do + #{ _('New file') } + %li + = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do + #{ _('Upload file') } + %li + = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do + #{ _('New directory') } + - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) + %li + - continue_params = { to: project_new_blob_path(@project, @id), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) + = link_to fork_path, method: :post do + #{ _('New file') } + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to upload a file again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) + = link_to fork_path, method: :post do + #{ _('Upload file') } + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to create a new directory again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) + = link_to fork_path, method: :post do + #{ _('New directory') } - - if can?(current_user, :push_code, @project) - %li.divider - %li.dropdown-header - #{ _('This repository') } - %li - = link_to new_project_branch_path(@project) do - #{ _('New branch') } - %li - = link_to new_project_tag_path(@project) do - #{ _('New tag') } + - if can?(current_user, :push_code, @project) + %li.divider + %li.dropdown-header + #{ _('This repository') } + %li + = link_to new_project_branch_path(@project) do + #{ _('New branch') } + %li + = link_to new_project_tag_path(@project) do + #{ _('New tag') } .tree-controls = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9e7ff8d1847..44dda25c38c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4389,6 +4389,9 @@ msgstr "" msgid "Files" msgstr "" +msgid "Files breadcrumb" +msgstr "" + msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}" msgstr "" diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 6762460971f..44715261b8b 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do let(:project_maintainer) { project.owner } before do + stub_feature_flags(vue_file_list: false) project.repository.delete_file(project_maintainer, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master') sign_in(project_maintainer) diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index dd2964c2186..69f8bd4d319 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -12,6 +12,7 @@ describe 'Projects > Files > User creates files' do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) stub_feature_flags(web_ide_default: false) project.add_maintainer(user) diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb index 24777788248..46586b891e7 100644 --- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb +++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb @@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do let(:user) { create(:user) } before do + stub_feature_flags(vue_file_list: false) project.add_developer(user) sign_in(user) end diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js new file mode 100644 index 00000000000..068fa317a87 --- /dev/null +++ b/spec/frontend/repository/components/breadcrumbs_spec.js @@ -0,0 +1,44 @@ +import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import Breadcrumbs from '~/repository/components/breadcrumbs.vue'; + +let vm; + +function factory(currentPath) { + vm = shallowMount(Breadcrumbs, { + propsData: { + currentPath, + }, + stubs: { + RouterLink: RouterLinkStub, + }, + }); +} + +describe('Repository breadcrumbs component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + path | linkCount + ${'/'} | ${1} + ${'app'} | ${2} + ${'app/assets'} | ${3} + ${'app/assets/javascripts'} | ${4} + `('renders $linkCount links for path $path', ({ path, linkCount }) => { + factory(path); + + expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount); + }); + + it('renders last link as active', () => { + factory('app/assets'); + + expect( + vm + .findAll(RouterLinkStub) + .at(2) + .attributes('aria-current'), + ).toEqual('page'); + }); +}); |