diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 70918dd55e4b631ae8ef1ba470378cd0f5383dd5..8ea5fce92fa7c74db496a1325070c079abd5f9f8 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -12,6 +12,7 @@ import { escapeRegExp } from 'lodash';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getRefMixin from '../../mixins/get_ref';
import commitQuery from '../../queries/commit.query.graphql';
@@ -41,7 +42,7 @@ export default {
},
},
},
- mixins: [getRefMixin],
+ mixins: [getRefMixin, glFeatureFlagMixin()],
props: {
id: {
type: String,
@@ -103,10 +104,21 @@ export default {
};
},
computed: {
+ refactorBlobViewerEnabled() {
+ return this.glFeatures.refactorBlobViewer;
+ },
routerLinkTo() {
- return this.isFolder
- ? { path: `/-/tree/${this.escapedRef}/${escapeFileUrl(this.path)}` }
- : null;
+ const blobRouteConfig = { path: `/-/blob/${this.escapedRef}/${escapeFileUrl(this.path)}` };
+ const treeRouteConfig = { path: `/-/tree/${this.escapedRef}/${escapeFileUrl(this.path)}` };
+
+ if (this.refactorBlobViewerEnabled && this.isBlob) {
+ return blobRouteConfig;
+ }
+
+ return this.isFolder ? treeRouteConfig : null;
+ },
+ isBlob() {
+ return this.type === 'blob';
},
isFolder() {
return this.type === 'tree';
@@ -115,7 +127,7 @@ export default {
return this.type === 'commit';
},
linkComponent() {
- return this.isFolder ? 'router-link' : 'a';
+ return this.isFolder || (this.refactorBlobViewerEnabled && this.isBlob) ? 'router-link' : 'a';
},
fullPath() {
return this.path.replace(new RegExp(`^${escapeRegExp(this.currentPath)}/`), '');
diff --git a/app/assets/javascripts/repository/pages/blob.vue b/app/assets/javascripts/repository/pages/blob.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c492f9663644aa98177e5d475335d7e685ea46d1
--- /dev/null
+++ b/app/assets/javascripts/repository/pages/blob.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
index ad6e32d7055c7b86a1a7d1c6c78b201ace6f4918..c7f7451fb55f726119638ed6f700b0f46b0a22f0 100644
--- a/app/assets/javascripts/repository/router.js
+++ b/app/assets/javascripts/repository/router.js
@@ -2,6 +2,7 @@ import { escapeRegExp } from 'lodash';
import Vue from 'vue';
import VueRouter from 'vue-router';
import { joinPaths } from '../lib/utils/url_utility';
+import BlobPage from './pages/blob.vue';
import IndexPage from './pages/index.vue';
import TreePage from './pages/tree.vue';
@@ -15,6 +16,13 @@ export default function createRouter(base, baseRef) {
}),
};
+ const blobPathRoute = {
+ component: BlobPage,
+ props: (route) => ({
+ path: route.params.path,
+ }),
+ };
+
return new VueRouter({
mode: 'history',
base: joinPaths(gon.relative_url_root || '', base),
@@ -31,6 +39,18 @@ export default function createRouter(base, baseRef) {
path: `(/-)?/tree/${escapeRegExp(baseRef)}/:path*`,
...treePathRoute,
},
+ {
+ name: 'blobPathDecoded',
+ // Sometimes the ref needs decoding depending on how the backend sends it to us
+ path: `(/-)?/blob/${decodeURI(baseRef)}/:path*`,
+ ...blobPathRoute,
+ },
+ {
+ name: 'blobPath',
+ // Support without decoding as well just in case the ref doesn't need to be decoded
+ path: `(/-)?/blob/${escapeRegExp(baseRef)}/:path*`,
+ ...blobPathRoute,
+ },
{
path: '/',
name: 'projectRoot',
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 00390d90e8db974c10aeffa20c08f03fa6fc3f21..4f28207564aec1a457fefd931804e87e165efaaf 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -35,6 +35,10 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:allow_editing_commit_messages, @project)
end
+ before_action do
+ push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
+ end
+
layout :determine_layout
feature_category :projects, [
diff --git a/config/feature_flags/development/refactor_blob_viewer.yml b/config/feature_flags/development/refactor_blob_viewer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..231e26840234cde7fa6f8b23612546ab4c627786
--- /dev/null
+++ b/config/feature_flags/development/refactor_blob_viewer.yml
@@ -0,0 +1,8 @@
+---
+name: refactor_blob_viewer
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57326
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324351
+milestone: '13.11'
+type: development
+group: group::source code
+default_enabled: false
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 4730679feb86d425e48036f3572271721816fed3..34601cab24fac8d9088aa1c5b83fab3464d24d63 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -290,6 +290,7 @@
let(:project) { create(:forked_project_with_submodules) }
before do
+ stub_feature_flags(refactor_blob_viewer: false)
project.add_maintainer(user)
sign_in user
visit project_path(project)
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 69cb69de5df5f959aac7e27c990a35ad153db3a0..3ebffbedcdba8daae5de94caf730499d8026d4be 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -19,6 +19,9 @@ function factory(propsData = {}) {
projectPath: 'gitlab-org/gitlab-ce',
url: `https://test.com`,
},
+ provide: {
+ glFeatures: { refactorBlobViewer: true },
+ },
mocks: {
$router,
},
@@ -81,7 +84,7 @@ describe('Repository table row component', () => {
it.each`
type | component | componentName
${'tree'} | ${RouterLinkStub} | ${'RouterLink'}
- ${'file'} | ${'a'} | ${'hyperlink'}
+ ${'blob'} | ${RouterLinkStub} | ${'RouterLink'}
${'commit'} | ${'a'} | ${'hyperlink'}
`('renders a $componentName for type $type', ({ type, component }) => {
factory({
diff --git a/spec/frontend/repository/router_spec.js b/spec/frontend/repository/router_spec.js
index 3c7dda05ca3d8f828efa510847e47ab42fbe87d2..3354b2315fca9cd71e0a29d2506d24fe14ac2e17 100644
--- a/spec/frontend/repository/router_spec.js
+++ b/spec/frontend/repository/router_spec.js
@@ -1,3 +1,4 @@
+import BlobPage from '~/repository/pages/blob.vue';
import IndexPage from '~/repository/pages/index.vue';
import TreePage from '~/repository/pages/tree.vue';
import createRouter from '~/repository/router';
@@ -11,6 +12,7 @@ describe('Repository router spec', () => {
${'/-/tree/master'} | ${'master'} | ${TreePage} | ${'TreePage'}
${'/-/tree/master/app/assets'} | ${'master'} | ${TreePage} | ${'TreePage'}
${'/-/tree/123/app/assets'} | ${'master'} | ${null} | ${'null'}
+ ${'/-/blob/master/file.md'} | ${'master'} | ${BlobPage} | ${'BlobPage'}
`('sets component as $componentName for path "$path"', ({ path, component, branch }) => {
const router = createRouter('', branch);