Merge branch 'vue-repo-breadcrumbs' into 'master'
Added repository file listing breadcrumbs See merge request gitlab-org/gitlab-ce!28706
This commit is contained in:
commit
4ca791e697
14 changed files with 208 additions and 69 deletions
61
app/assets/javascripts/repository/components/breadcrumbs.vue
Normal file
61
app/assets/javascripts/repository/components/breadcrumbs.vue
Normal file
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
query getProjectShortPath {
|
||||
projectShortPath @client
|
||||
}
|
|
@ -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 === '';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -4386,6 +4386,9 @@ msgstr ""
|
|||
msgid "Files"
|
||||
msgstr ""
|
||||
|
||||
msgid "Files breadcrumb"
|
||||
msgstr ""
|
||||
|
||||
msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
44
spec/frontend/repository/components/breadcrumbs_spec.js
Normal file
44
spec/frontend/repository/components/breadcrumbs_spec.js
Normal file
|
@ -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');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue