diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-11 09:13:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-11 09:13:09 +0300 |
commit | be7d70b884e6fa66c52862f38bf0f39b0631868b (patch) | |
tree | 235616671718bf2f39855f663677b61a55a8d68c /app/assets | |
parent | 848ba57883b4ea9164bcb56a16c0fcb2b55b56e6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
9 files changed, 297 insertions, 16 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js index ebf2ab0381e..b27dccabdf8 100644 --- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -306,6 +306,12 @@ export const GO_TO_PROJECT_WIKI = { 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 = { id: 'projectFiles.moveSelectionUp', description: __('Move selection up'), @@ -549,6 +555,7 @@ export const PROJECT_SHORTCUTS_GROUP = { GO_TO_PROJECT_KUBERNETES, GO_TO_PROJECT_SNIPPETS, GO_TO_PROJECT_WIKI, + GO_TO_PROJECT_WEBIDE, ], }; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js index b188d3b0ec3..7d8e4dd490c 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js @@ -1,4 +1,5 @@ import Mousetrap from 'mousetrap'; +import { visitUrl, constructWebIDEPath } from '~/lib/utils/url_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility'; import { keysFor, @@ -18,6 +19,7 @@ import { GO_TO_PROJECT_KUBERNETES, GO_TO_PROJECT_ENVIRONMENTS, GO_TO_PROJECT_METRICS, + GO_TO_PROJECT_WEBIDE, NEW_ISSUE, } from './keybindings'; import Shortcuts from './shortcuts'; @@ -58,6 +60,18 @@ export default class ShortcutsNavigation extends Shortcuts { findAndFollowLink('.shortcuts-environments'), ); 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')); } + + 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); + } + } } diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 6580a028e4a..1c22d21a313 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -590,3 +590,30 @@ export function isSameOriginUrl(url) { 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}`), + ); +} diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index db84e2b5912..d3717f10ec7 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -9,11 +9,13 @@ import { } from '@gitlab/ui'; import permissionsQuery from 'shared_queries/repository/permissions.query.graphql'; import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { __ } from '../../locale'; import getRefMixin from '../mixins/get_ref'; import projectPathQuery from '../queries/project_path.query.graphql'; import projectShortPathQuery from '../queries/project_short_path.query.graphql'; import UploadBlobModal from './upload_blob_modal.vue'; +import NewDirectoryModal from './new_directory_modal.vue'; const ROW_TYPES = { header: 'header', @@ -21,6 +23,7 @@ const ROW_TYPES = { }; const UPLOAD_BLOB_MODAL_ID = 'modal-upload-blob'; +const NEW_DIRECTORY_MODAL_ID = 'modal-new-directory'; export default { components: { @@ -30,6 +33,7 @@ export default { GlDropdownItem, GlIcon, UploadBlobModal, + NewDirectoryModal, }, apollo: { projectShortPath: { @@ -54,7 +58,7 @@ export default { directives: { GlModal: GlModalDirective, }, - mixins: [getRefMixin], + mixins: [getRefMixin, glFeatureFlagsMixin()], props: { currentPath: { type: String, @@ -121,8 +125,14 @@ export default { required: false, default: '', }, + newDirPath: { + type: String, + required: false, + default: '', + }, }, uploadBlobModalId: UPLOAD_BLOB_MODAL_ID, + newDirectoryModalId: NEW_DIRECTORY_MODAL_ID, data() { return { projectShortPath: '', @@ -160,6 +170,13 @@ export default { showUploadModal() { return this.canEditTree && !this.$apollo.queries.userPermissions.loading; }, + showNewDirectoryModal() { + return ( + this.glFeatures.newDirModal && + this.canEditTree && + !this.$apollo.queries.userPermissions.loading + ); + }, dropdownItems() { const items = []; @@ -185,15 +202,26 @@ export default { text: __('Upload file'), modalId: UPLOAD_BLOB_MODAL_ID, }, - { + ); + + if (this.glFeatures.newDirModal) { + items.push({ + attrs: { + href: '#modal-create-new-dir', + }, + text: __('New directory'), + modalId: NEW_DIRECTORY_MODAL_ID, + }); + } else { + items.push({ attrs: { href: '#modal-create-new-dir', 'data-target': '#modal-create-new-dir', 'data-toggle': 'modal', }, text: __('New directory'), - }, - ); + }); + } } else if (this.canCreateMrFromFork) { items.push( { @@ -306,5 +334,14 @@ export default { :can-push-code="canPushCode" :path="uploadPath" /> + <new-directory-modal + v-if="showNewDirectoryModal" + :can-push-code="canPushCode" + :modal-id="$options.newDirectoryModalId" + :commit-message="__('Add new directory')" + :target-branch="selectedBranch" + :original-branch="originalBranch" + :path="newDirPath" + /> </nav> </template> diff --git a/app/assets/javascripts/repository/components/new_directory_modal.vue b/app/assets/javascripts/repository/components/new_directory_modal.vue new file mode 100644 index 00000000000..6c5797bf5b2 --- /dev/null +++ b/app/assets/javascripts/repository/components/new_directory_modal.vue @@ -0,0 +1,183 @@ +<script> +import { + GlAlert, + GlForm, + GlModal, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlToggle, +} from '@gitlab/ui'; +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; +import { + SECONDARY_OPTIONS_TEXT, + COMMIT_LABEL, + TARGET_BRANCH_LABEL, + TOGGLE_CREATE_MR_LABEL, + NEW_BRANCH_IN_FORK, +} from '../constants'; + +const MODAL_TITLE = __('Create New Directory'); +const PRIMARY_OPTIONS_TEXT = __('Create directory'); +const DIR_LABEL = __('Directory name'); +const ERROR_MESSAGE = __('Error creating new directory. Please try again.'); + +export default { + components: { + GlAlert, + GlModal, + GlForm, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlToggle, + }, + i18n: { + DIR_LABEL, + COMMIT_LABEL, + TARGET_BRANCH_LABEL, + TOGGLE_CREATE_MR_LABEL, + NEW_BRANCH_IN_FORK, + PRIMARY_OPTIONS_TEXT, + ERROR_MESSAGE, + }, + props: { + modalTitle: { + type: String, + default: MODAL_TITLE, + required: false, + }, + modalId: { + type: String, + required: true, + }, + primaryBtnText: { + type: String, + default: PRIMARY_OPTIONS_TEXT, + required: false, + }, + commitMessage: { + type: String, + required: true, + }, + targetBranch: { + type: String, + required: true, + }, + originalBranch: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + canPushCode: { + type: Boolean, + required: true, + }, + }, + data() { + return { + dir: null, + commit: this.commitMessage, + target: this.targetBranch, + createNewMr: true, + loading: false, + }; + }, + computed: { + primaryOptions() { + return { + text: this.primaryBtnText, + attributes: [ + { + variant: 'confirm', + loading: this.loading, + disabled: !this.formCompleted || this.loading, + }, + ], + }; + }, + cancelOptions() { + return { + text: SECONDARY_OPTIONS_TEXT, + attributes: [ + { + disabled: this.loading, + }, + ], + }; + }, + showCreateNewMrToggle() { + return this.canPushCode; + }, + formCompleted() { + return this.dir && this.commit && this.target; + }, + }, + methods: { + submitForm() { + this.loading = true; + + const formData = new FormData(); + formData.append('dir_name', this.dir); + formData.append('commit_message', this.commit); + formData.append('branch_name', this.target); + formData.append('original_branch', this.originalBranch); + + if (this.createNewMr) { + formData.append('create_merge_request', this.createNewMr); + } + + return axios + .post(this.path, formData) + .then((response) => { + visitUrl(response.data.filePath); + }) + .catch(() => { + this.loading = false; + createFlash({ message: ERROR_MESSAGE }); + }); + }, + }, +}; +</script> + +<template> + <gl-form> + <gl-modal + :modal-id="modalId" + :title="modalTitle" + :action-primary="primaryOptions" + :action-cancel="cancelOptions" + @primary.prevent="submitForm" + > + <gl-form-group :label="$options.i18n.DIR_LABEL" label-for="dir_name"> + <gl-form-input v-model="dir" :disabled="loading" name="dir_name" /> + </gl-form-group> + <gl-form-group :label="$options.i18n.COMMIT_LABEL" label-for="commit_message"> + <gl-form-textarea v-model="commit" name="commit_message" :disabled="loading" /> + </gl-form-group> + <gl-form-group + v-if="canPushCode" + :label="$options.i18n.TARGET_BRANCH_LABEL" + label-for="branch_name" + > + <gl-form-input v-model="target" :disabled="loading" name="branch_name" /> + </gl-form-group> + <gl-toggle + v-if="showCreateNewMrToggle" + v-model="createNewMr" + :disabled="loading" + :label="$options.i18n.TOGGLE_CREATE_MR_LABEL" + /> + <gl-alert v-if="!canPushCode" variant="info" :dismissible="false" class="gl-mt-3"> + {{ $options.i18n.NEW_BRANCH_IN_FORK }} + </gl-alert> + </gl-modal> + </gl-form> +</template> diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index 70952c8413b..152fabbd7cc 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -10,6 +10,9 @@ export const SECONDARY_OPTIONS_TEXT = __('Cancel'); export const COMMIT_LABEL = __('Commit message'); export const TARGET_BRANCH_LABEL = __('Target branch'); export const TOGGLE_CREATE_MR_LABEL = __('Start a new merge request with these changes'); +export const NEW_BRANCH_IN_FORK = __( + 'A new branch will be created in your fork and a new merge request will be started.', +); export const COMMIT_MESSAGE_SUBJECT_MAX_LENGTH = 52; export const COMMIT_MESSAGE_BODY_MAX_LENGTH = 72; diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 60a1a0443f7..45e026ad695 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -120,6 +120,7 @@ export default function setupVueRepositoryList() { forkNewDirectoryPath, forkUploadBlobPath, uploadPath, + newDirPath, }, }); }, diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js index 6637d03a7a4..0a675e14eb5 100644 --- a/app/assets/javascripts/repository/router.js +++ b/app/assets/javascripts/repository/router.js @@ -1,7 +1,7 @@ import { escapeRegExp } from 'lodash'; import Vue from 'vue'; 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 IndexPage from './pages/index.vue'; import TreePage from './pages/tree.vue'; @@ -24,7 +24,7 @@ export default function createRouter(base, baseRef) { }), }; - return new VueRouter({ + const router = new VueRouter({ mode: 'history', base: joinPaths(gon.relative_url_root || '', base), routes: [ @@ -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; } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index 966262944ad..ecabe5007e6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -10,7 +10,7 @@ import { GlSafeHtmlDirective as SafeHtml, GlSprintf, } from '@gitlab/ui'; -import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; +import { constructWebIDEPath } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; @@ -58,15 +58,7 @@ export default { }); }, webIdePath() { - return mergeUrlParams( - { - target_project: - this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath - ? this.mr.targetProjectFullPath - : '', - }, - webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`), - ); + return constructWebIDEPath(this.mr); }, isFork() { return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath; |