Commit 6f05c324 authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch...

Merge branch '340095-add-a-keyboard-shortcut-to-quickly-open-the-web-ide-from-any-repo-view' into 'master'

Resolve "Add a keyboard shortcut to quickly open the Web IDE from any repo view"

See merge request gitlab-org/gitlab!71679
parents 69585491 c3b1b2a3
...@@ -306,6 +306,12 @@ export const GO_TO_PROJECT_WIKI = { ...@@ -306,6 +306,12 @@ export const GO_TO_PROJECT_WIKI = {
defaultKeys: ['g w'], // eslint-disable-line @gitlab/require-i18n-strings defaultKeys: ['g w'], // eslint-disable-line @gitlab/require-i18n-strings
}; };
export const GO_TO_PROJECT_WEBIDE = {
id: 'project.goToWebIDE',
description: __('Open in Web IDE'),
defaultKeys: ['.'],
};
export const PROJECT_FILES_MOVE_SELECTION_UP = { export const PROJECT_FILES_MOVE_SELECTION_UP = {
id: 'projectFiles.moveSelectionUp', id: 'projectFiles.moveSelectionUp',
description: __('Move selection up'), description: __('Move selection up'),
...@@ -549,6 +555,7 @@ export const PROJECT_SHORTCUTS_GROUP = { ...@@ -549,6 +555,7 @@ export const PROJECT_SHORTCUTS_GROUP = {
GO_TO_PROJECT_KUBERNETES, GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_SNIPPETS, GO_TO_PROJECT_SNIPPETS,
GO_TO_PROJECT_WIKI, GO_TO_PROJECT_WIKI,
GO_TO_PROJECT_WEBIDE,
], ],
}; };
......
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { visitUrl, constructWebIDEPath } from '~/lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility';
import { import {
keysFor, keysFor,
...@@ -18,6 +19,7 @@ import { ...@@ -18,6 +19,7 @@ import {
GO_TO_PROJECT_KUBERNETES, GO_TO_PROJECT_KUBERNETES,
GO_TO_PROJECT_ENVIRONMENTS, GO_TO_PROJECT_ENVIRONMENTS,
GO_TO_PROJECT_METRICS, GO_TO_PROJECT_METRICS,
GO_TO_PROJECT_WEBIDE,
NEW_ISSUE, NEW_ISSUE,
} from './keybindings'; } from './keybindings';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
...@@ -58,6 +60,18 @@ export default class ShortcutsNavigation extends Shortcuts { ...@@ -58,6 +60,18 @@ export default class ShortcutsNavigation extends Shortcuts {
findAndFollowLink('.shortcuts-environments'), findAndFollowLink('.shortcuts-environments'),
); );
Mousetrap.bind(keysFor(GO_TO_PROJECT_METRICS), () => findAndFollowLink('.shortcuts-metrics')); Mousetrap.bind(keysFor(GO_TO_PROJECT_METRICS), () => findAndFollowLink('.shortcuts-metrics'));
Mousetrap.bind(keysFor(GO_TO_PROJECT_WEBIDE), ShortcutsNavigation.navigateToWebIDE);
Mousetrap.bind(keysFor(NEW_ISSUE), () => findAndFollowLink('.shortcuts-new-issue')); Mousetrap.bind(keysFor(NEW_ISSUE), () => findAndFollowLink('.shortcuts-new-issue'));
} }
static navigateToWebIDE() {
const path = constructWebIDEPath({
sourceProjectFullPath: window.gl.mrWidgetData?.source_project_full_path,
targetProjectFullPath: window.gl.mrWidgetData?.target_project_full_path,
iid: window.gl.mrWidgetData?.iid,
});
if (path) {
visitUrl(path);
}
}
} }
...@@ -590,3 +590,30 @@ export function isSameOriginUrl(url) { ...@@ -590,3 +590,30 @@ export function isSameOriginUrl(url) {
return false; return false;
} }
} }
/**
* Returns a URL to WebIDE considering the current user's position in
* repository's tree. If not MR `iid` has been passed, the URL is fetched
* from the global `gl.webIDEPath`.
*
* @param sourceProjectFullPath Source project's full path. Used in MRs
* @param targetProjectFullPath Target project's full path. Used in MRs
* @param iid MR iid
* @returns {string}
*/
export function constructWebIDEPath({
sourceProjectFullPath,
targetProjectFullPath = '',
iid,
} = {}) {
if (!iid || !sourceProjectFullPath) {
return window.gl?.webIDEPath;
}
return mergeUrlParams(
{
target_project: sourceProjectFullPath !== targetProjectFullPath ? targetProjectFullPath : '',
},
webIDEUrl(`/${sourceProjectFullPath}/merge_requests/${iid}`),
);
}
import { escapeRegExp } from 'lodash'; import { escapeRegExp } from 'lodash';
import Vue from 'vue'; import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import { joinPaths } from '../lib/utils/url_utility'; import { joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
import BlobPage from './pages/blob.vue'; import BlobPage from './pages/blob.vue';
import IndexPage from './pages/index.vue'; import IndexPage from './pages/index.vue';
import TreePage from './pages/tree.vue'; import TreePage from './pages/tree.vue';
...@@ -24,7 +24,7 @@ export default function createRouter(base, baseRef) { ...@@ -24,7 +24,7 @@ export default function createRouter(base, baseRef) {
}), }),
}; };
return new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
base: joinPaths(gon.relative_url_root || '', base), base: joinPaths(gon.relative_url_root || '', base),
routes: [ routes: [
...@@ -59,4 +59,21 @@ export default function createRouter(base, baseRef) { ...@@ -59,4 +59,21 @@ export default function createRouter(base, baseRef) {
}, },
], ],
}); });
router.afterEach((to) => {
const needsClosingSlash = !to.name.includes('blobPath');
window.gl.webIDEPath = webIDEUrl(
joinPaths(
'/',
base,
'edit',
decodeURI(baseRef),
'-',
to.params.path || '',
needsClosingSlash && '/',
),
);
});
return router;
} }
...@@ -10,7 +10,7 @@ import { ...@@ -10,7 +10,7 @@ import {
GlSafeHtmlDirective as SafeHtml, GlSafeHtmlDirective as SafeHtml,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; import { constructWebIDEPath } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
...@@ -58,15 +58,7 @@ export default { ...@@ -58,15 +58,7 @@ export default {
}); });
}, },
webIdePath() { webIdePath() {
return mergeUrlParams( return constructWebIDEPath(this.mr);
{
target_project:
this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath
? this.mr.targetProjectFullPath
: '',
},
webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`),
);
}, },
isFork() { isFork() {
return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath; return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath;
......
...@@ -18,3 +18,4 @@ ...@@ -18,3 +18,4 @@
= render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
= render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration? = render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration?
= render 'shared/web_ide_path'
...@@ -99,3 +99,4 @@ ...@@ -99,3 +99,4 @@
= render 'projects/invite_members_modal', project: @project = render 'projects/invite_members_modal', project: @project
- if Gitlab::CurrentSettings.gitpod_enabled && !current_user&.gitpod_enabled - if Gitlab::CurrentSettings.gitpod_enabled && !current_user&.gitpod_enabled
= render 'shared/gitpod/enable_gitpod_modal' = render 'shared/gitpod/enable_gitpod_modal'
= render 'shared/web_ide_path'
...@@ -11,3 +11,4 @@ ...@@ -11,3 +11,4 @@
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
= render 'shared/web_ide_path'
= javascript_tag do
:plain
window.gl = window.gl || {};
window.gl.webIDEPath = '#{web_ide_url}'
...@@ -23915,6 +23915,9 @@ msgstr "" ...@@ -23915,6 +23915,9 @@ msgstr ""
msgid "Open errors" msgid "Open errors"
msgstr "" msgstr ""
msgid "Open in Web IDE"
msgstr ""
msgid "Open in file view" msgid "Open in file view"
msgstr "" msgstr ""
......
...@@ -1004,4 +1004,39 @@ describe('URL utility', () => { ...@@ -1004,4 +1004,39 @@ describe('URL utility', () => {
expect(urlUtils.isSameOriginUrl(url)).toBe(expected); expect(urlUtils.isSameOriginUrl(url)).toBe(expected);
}); });
}); });
describe('constructWebIDEPath', () => {
let originalGl;
const projectIDEPath = '/foo/bar';
const sourceProj = 'my_-fancy-proj/boo';
const targetProj = 'boo/another-fancy-proj';
const mrIid = '7';
beforeEach(() => {
originalGl = window.gl;
window.gl = { webIDEPath: projectIDEPath };
});
afterEach(() => {
window.gl = originalGl;
});
it.each`
sourceProjectFullPath | targetProjectFullPath | iid | expectedPath
${undefined} | ${undefined} | ${undefined} | ${projectIDEPath}
${undefined} | ${undefined} | ${mrIid} | ${projectIDEPath}
${undefined} | ${targetProj} | ${undefined} | ${projectIDEPath}
${undefined} | ${targetProj} | ${mrIid} | ${projectIDEPath}
${sourceProj} | ${undefined} | ${undefined} | ${projectIDEPath}
${sourceProj} | ${targetProj} | ${undefined} | ${projectIDEPath}
${sourceProj} | ${undefined} | ${mrIid} | ${`/-/ide/project/${sourceProj}/merge_requests/${mrIid}?target_project=`}
${sourceProj} | ${sourceProj} | ${mrIid} | ${`/-/ide/project/${sourceProj}/merge_requests/${mrIid}?target_project=`}
${sourceProj} | ${targetProj} | ${mrIid} | ${`/-/ide/project/${sourceProj}/merge_requests/${mrIid}?target_project=${encodeURIComponent(targetProj)}`}
`(
'returns $expectedPath for "$sourceProjectFullPath + $targetProjectFullPath + $iid"',
({ expectedPath, ...args } = {}) => {
expect(urlUtils.constructWebIDEPath(args)).toBe(expectedPath);
},
);
});
}); });
...@@ -24,4 +24,32 @@ describe('Repository router spec', () => { ...@@ -24,4 +24,32 @@ describe('Repository router spec', () => {
expect(componentsForRoute).toContain(component); expect(componentsForRoute).toContain(component);
} }
}); });
describe('Storing Web IDE path globally', () => {
const proj = 'foo-bar-group/foo-bar-proj';
let originalGl;
beforeEach(() => {
originalGl = window.gl;
});
afterEach(() => {
window.gl = originalGl;
});
it.each`
path | branch | expectedPath
${'/'} | ${'main'} | ${`/-/ide/project/${proj}/edit/main/-/`}
${'/tree/main'} | ${'main'} | ${`/-/ide/project/${proj}/edit/main/-/`}
${'/tree/feat(test)'} | ${'feat(test)'} | ${`/-/ide/project/${proj}/edit/feat(test)/-/`}
${'/-/tree/main'} | ${'main'} | ${`/-/ide/project/${proj}/edit/main/-/`}
${'/-/tree/main/app/assets'} | ${'main'} | ${`/-/ide/project/${proj}/edit/main/-/app/assets/`}
${'/-/blob/main/file.md'} | ${'main'} | ${`/-/ide/project/${proj}/edit/main/-/file.md`}
`('generates the correct Web IDE url for $path', ({ path, branch, expectedPath } = {}) => {
const router = createRouter(proj, branch);
router.push(path);
expect(window.gl.webIDEPath).toBe(expectedPath);
});
});
}); });
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