Commit 1224aa51 authored by Phil Hughes's avatar Phil Hughes

Created repository list breadcrumbs Vue app

parent 4a938724
<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>
import Vue from 'vue'; import Vue from 'vue';
import createRouter from './router'; import createRouter from './router';
import App from './components/app.vue'; import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { setTitle } from './utils/title'; import { setTitle } from './utils/title';
export default function setupVueRepositoryList() { export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list'); 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); const router = createRouter(projectPath, ref);
apolloProvider.clients.defaultClient.cache.writeData({ apolloProvider.clients.defaultClient.cache.writeData({
data: { data: {
projectPath, projectPath,
projectShortPath,
ref, ref,
}, },
}); });
router.afterEach(({ params: { pathMatch } }) => setTitle(pathMatch, ref, fullName)); router.afterEach(({ params: { pathMatch } }) => {
router.afterEach(to => { const isRoot = pathMatch === undefined || pathMatch === '/';
const isRoot = to.params.pathMatch === undefined || to.params.pathMatch === '/';
setTitle(pathMatch, ref, fullName);
if (!isRoot) { if (!isRoot) {
document document
...@@ -31,6 +34,20 @@ export default function setupVueRepositoryList() { ...@@ -31,6 +34,20 @@ export default function setupVueRepositoryList() {
.forEach(elem => elem.classList.toggle('hidden', !isRoot)); .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({ return new Vue({
el, el,
router, router,
......
query getProjectShortPath {
projectShortPath @client
}
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const setTitle = (pathMatch, ref, project) => { export const setTitle = (pathMatch, ref, project) => {
if (!pathMatch) return;
const path = pathMatch.replace(/^\//, ''); const path = pathMatch.replace(/^\//, '');
const isEmpty = path === ''; const isEmpty = path === '';
......
...@@ -655,4 +655,8 @@ module ProjectsHelper ...@@ -655,4 +655,8 @@ module ProjectsHelper
project.builds_enabled? && project.builds_enabled? &&
!project.repository.gitlab_ci_yml !project.repository.gitlab_ci_yml
end end
def vue_file_list_enabled?
Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project)
end
end end
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
- project = local_assigns.fetch(:project) { @project } - 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) } - 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) - show_auto_devops_callout = show_auto_devops_callout?(@project)
- vue_file_list = Feature.enabled?(:vue_file_list, @project)
#tree-holder.tree-holder.clearfix #tree-holder.tree-holder.clearfix
.nav-block .nav-block
...@@ -14,11 +13,11 @@ ...@@ -14,11 +13,11 @@
= render 'shared/commit_well', commit: commit, ref: ref, project: project = render 'shared/commit_well', commit: commit, ref: ref, project: project
- if is_project_overview - 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) = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- if vue_file_list - if vue_file_list_enabled?
#js-tree-list{ data: { project_path: @project.full_path, full_name: @project.name_with_namespace, ref: ref } } #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 - if @tree.readme
= render "projects/tree/readme", readme: @tree.readme = render "projects/tree/readme", readme: @tree.readme
- else - else
......
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project) - show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15 - 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 .row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex .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 .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
......
- if readme.rich_viewer - 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 .js-file-title.file-title
= blob_icon readme.mode, readme.name = blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
......
...@@ -6,71 +6,74 @@ ...@@ -6,71 +6,74 @@
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true = render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- if on_top_of_branch? - 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 - else
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' } - 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 - if vue_file_list_enabled?
%li.breadcrumb-item #js-repo-breadcrumb
= link_to project_tree_path(@project, @ref) do - else
= @project.path %ul.breadcrumb.repo-breadcrumb
- path_breadcrumbs do |title, path|
%li.breadcrumb-item %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 - if can_collaborate || can_create_mr_from_fork
%li.breadcrumb-item %li.breadcrumb-item
%a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes } %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('plus', size: 16, css_class: 'float-left')
= sprite_icon('arrow-down', size: 16, css_class: 'float-left') = sprite_icon('arrow-down', size: 16, css_class: 'float-left')
- if on_top_of_branch? - if on_top_of_branch?
.add-to-tree-dropdown .add-to-tree-dropdown
%ul.dropdown-menu %ul.dropdown-menu
- if can_edit_tree? - if can_edit_tree?
%li.dropdown-header %li.dropdown-header
#{ _('This directory') } #{ _('This directory') }
%li %li
= link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
#{ _('New file') } #{ _('New file') }
%li %li
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
#{ _('Upload file') } #{ _('Upload file') }
%li %li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
#{ _('New directory') } #{ _('New directory') }
- elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
%li %li
- continue_params = { to: project_new_blob_path(@project, @id), - continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice, notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
#{ _('New file') } #{ _('New file') }
%li %li
- continue_params = { to: request.fullpath, - continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.", notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
#{ _('Upload file') } #{ _('Upload file') }
%li %li
- continue_params = { to: request.fullpath, - continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.", notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
#{ _('New directory') } #{ _('New directory') }
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%li.divider %li.divider
%li.dropdown-header %li.dropdown-header
#{ _('This repository') } #{ _('This repository') }
%li %li
= link_to new_project_branch_path(@project) do = link_to new_project_branch_path(@project) do
#{ _('New branch') } #{ _('New branch') }
%li %li
= link_to new_project_tag_path(@project) do = link_to new_project_tag_path(@project) do
#{ _('New tag') } #{ _('New tag') }
.tree-controls .tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
......
...@@ -4389,6 +4389,9 @@ msgstr "" ...@@ -4389,6 +4389,9 @@ msgstr ""
msgid "Files" msgid "Files"
msgstr "" msgstr ""
msgid "Files breadcrumb"
msgstr ""
msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}" msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
msgstr "" msgstr ""
......
...@@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do ...@@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
let(:project_maintainer) { project.owner } let(:project_maintainer) { project.owner }
before do before do
stub_feature_flags(vue_file_list: false)
project.repository.delete_file(project_maintainer, 'LICENSE', project.repository.delete_file(project_maintainer, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master') message: 'Remove LICENSE', branch_name: 'master')
sign_in(project_maintainer) sign_in(project_maintainer)
......
...@@ -12,6 +12,7 @@ describe 'Projects > Files > User creates files' do ...@@ -12,6 +12,7 @@ describe 'Projects > Files > User creates files' do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
stub_feature_flags(vue_file_list: false)
stub_feature_flags(web_ide_default: false) stub_feature_flags(web_ide_default: false)
project.add_maintainer(user) project.add_maintainer(user)
......
...@@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do ...@@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
stub_feature_flags(vue_file_list: false)
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
end end
......
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');
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment