Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2018-07-26 17:56:56 +0300
committerTim Zallmann <tzallmann@gitlab.com>2018-07-26 17:56:56 +0300
commitcded268ca1d49fe93b8e0940586e9792347c88c3 (patch)
tree9d84a5e8ae38e53514b6ab1f4e99d38ddf2fe138
parent8873840839811948b2f29175177b91bcf806a3f8 (diff)
Enable deleting files in the Web IDE
-rw-r--r--app/assets/javascripts/ide/components/changed_file_icon.vue20
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue10
-rw-r--r--app/assets/javascripts/ide/components/ide_review.vue13
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue8
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue6
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue52
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue17
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue8
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue6
-rw-r--r--app/assets/javascripts/ide/constants.js15
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions.js8
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js8
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js26
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js11
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js20
-rw-r--r--app/assets/javascripts/ide/stores/utils.js29
-rw-r--r--app/assets/javascripts/ide/utils.js12
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss63
-rw-r--r--changelogs/unreleased/ide-delete-entries.yml5
-rw-r--r--locale/gitlab.pot14
-rw-r--r--spec/javascripts/ide/components/changed_file_icon_spec.js8
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_item_spec.js16
-rw-r--r--spec/javascripts/ide/components/new_dropdown/index_spec.js11
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js3
-rw-r--r--spec/javascripts/ide/components/repo_file_spec.js19
-rw-r--r--spec/javascripts/ide/components/repo_tab_spec.js4
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js16
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js16
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js64
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js57
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js73
36 files changed, 545 insertions, 116 deletions
diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue
index a4e06bbbe3c..720ae11aaa6 100644
--- a/app/assets/javascripts/ide/components/changed_file_icon.vue
+++ b/app/assets/javascripts/ide/components/changed_file_icon.vue
@@ -3,6 +3,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
+import { getCommitIconMap } from '../utils';
export default {
components: {
@@ -34,16 +35,14 @@ export default {
},
computed: {
changedIcon() {
- const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
- return this.file.tempFile && !this.forceModifiedIcon
- ? `file-addition${suffix}`
- : `file-modified${suffix}`;
- },
- stagedIcon() {
- return `${this.changedIcon}-solid`;
+ const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : '';
+
+ if (this.forceModifiedIcon) return `file-modified${suffix}`;
+
+ return `${getCommitIconMap(this.file).icon}${suffix}`;
},
changedIconClass() {
- return `multi-${this.changedIcon} float-left`;
+ return `ide-${this.changedIcon} float-left`;
},
tooltipTitle() {
if (!this.showTooltip) return undefined;
@@ -66,6 +65,9 @@ export default {
return undefined;
},
+ showIcon() {
+ return this.file.changed || this.file.tempFile || this.file.staged || this.file.deleted;
+ },
},
};
</script>
@@ -79,7 +81,7 @@ export default {
class="ide-file-changed-icon"
>
<icon
- v-if="file.changed || file.tempFile || file.staged"
+ v-if="showIcon"
:name="changedIcon"
:size="12"
:css-classes="changedIconClass"
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index ee21eeda3cd..391004dcd3c 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue';
import { viewerTypes } from '../../constants';
+import { getCommitIconMap } from '../../utils';
export default {
components: {
@@ -42,11 +43,12 @@ export default {
},
computed: {
iconName() {
- const prefix = this.stagedList ? '-solid' : '';
- return this.file.tempFile ? `file-addition${prefix}` : `file-modified${prefix}`;
+ const suffix = this.stagedList ? '-solid' : '';
+
+ return `${getCommitIconMap(this.file).icon}${suffix}`;
},
iconClass() {
- return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
+ return `${getCommitIconMap(this.file).class} append-right-8`;
},
fullKey() {
return `${this.keyPrefix}-${this.file.key}`;
@@ -67,6 +69,8 @@ export default {
'stageChange',
]),
openFileInEditor() {
+ if (this.file.type === 'tree') return null;
+
return this.openPendingTab({
file: this.file,
keyPrefix: this.keyPrefix,
diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue
index f9978762c45..d09c99050fe 100644
--- a/app/assets/javascripts/ide/components/ide_review.vue
+++ b/app/assets/javascripts/ide/components/ide_review.vue
@@ -10,7 +10,7 @@ export default {
EditorModeDropdown,
},
computed: {
- ...mapGetters(['currentMergeRequest']),
+ ...mapGetters(['currentMergeRequest', 'activeFile']),
...mapState(['viewer', 'currentMergeRequestId']),
showLatestChangesText() {
return !this.currentMergeRequestId || this.viewer === viewerTypes.diff;
@@ -23,12 +23,20 @@ export default {
},
},
mounted() {
+ if (this.activeFile && this.activeFile.pending && !this.activeFile.deleted) {
+ this.$router.push(`/project${this.activeFile.url}`, () => {
+ this.updateViewer('editor');
+ });
+ } else if (this.activeFile && this.activeFile.deleted) {
+ this.resetOpenFiles();
+ }
+
this.$nextTick(() => {
this.updateViewer(this.currentMergeRequestId ? viewerTypes.mr : viewerTypes.diff);
});
},
methods: {
- ...mapActions(['updateViewer']),
+ ...mapActions(['updateViewer', 'resetOpenFiles']),
},
};
</script>
@@ -36,7 +44,6 @@ export default {
<template>
<ide-tree-list
:viewer-type="viewer"
- :disable-action-dropdown="true"
header-class="ide-review-header"
>
<template
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 0a95c0bb30d..e996dd9059e 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -17,14 +17,18 @@ export default {
...mapGetters(['currentProject', 'currentTree', 'activeFile']),
},
mounted() {
- if (this.activeFile && this.activeFile.pending) {
+ if (!this.activeFile) return;
+
+ if (this.activeFile.pending && !this.activeFile.deleted) {
this.$router.push(`/project${this.activeFile.url}`, () => {
this.updateViewer('editor');
});
+ } else if (this.activeFile.deleted) {
+ this.resetOpenFiles();
}
},
methods: {
- ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']),
+ ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry', 'resetOpenFiles']),
},
};
</script>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 0df99798d21..2e7226b727c 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -22,11 +22,6 @@ export default {
required: false,
default: null,
},
- disableActionDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
...mapState(['currentBranchId']),
@@ -69,7 +64,6 @@ export default {
:key="file.key"
:file="file"
:level="0"
- :disable-action-dropdown="disableActionDropdown"
/>
</template>
</div>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index c29e49ba766..440e480d596 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -13,7 +13,7 @@ export default {
ItemButton,
},
props: {
- branch: {
+ type: {
type: String,
required: true,
},
@@ -45,7 +45,7 @@ export default {
},
},
methods: {
- ...mapActions(['createTempEntry', 'openNewEntryModal']),
+ ...mapActions(['createTempEntry', 'openNewEntryModal', 'deleteEntry']),
createNewItem(type) {
this.openNewEntryModal({ type, path: this.path });
this.dropdownOpen = false;
@@ -82,28 +82,40 @@ export default {
ref="dropdownMenu"
class="dropdown-menu dropdown-menu-right"
>
+ <template v-if="type === 'tree'">
+ <li>
+ <item-button
+ :label="__('New file')"
+ class="d-flex"
+ icon="doc-new"
+ icon-classes="mr-2"
+ @click="createNewItem('blob')"
+ />
+ </li>
+ <li>
+ <upload
+ :path="path"
+ @create="createTempEntry"
+ />
+ </li>
+ <li>
+ <item-button
+ :label="__('New directory')"
+ class="d-flex"
+ icon="folder-new"
+ icon-classes="mr-2"
+ @click="createNewItem('tree')"
+ />
+ </li>
+ <li class="divider"></li>
+ </template>
<li>
<item-button
- :label="__('New file')"
+ :label="__('Delete')"
class="d-flex"
- icon="doc-new"
+ icon="remove"
icon-classes="mr-2"
- @click="createNewItem('blob')"
- />
- </li>
- <li>
- <upload
- :path="path"
- @create="createTempEntry"
- />
- </li>
- <li>
- <item-button
- :label="__('New directory')"
- class="d-flex"
- icon="folder-new"
- icon-classes="mr-2"
- @click="createNewItem('tree')"
+ @click="deleteEntry(path)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 50ab242ba2a..6f1a941fbc4 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -44,7 +44,7 @@ export default {
},
},
mounted() {
- if (this.lastOpenedFile) {
+ if (this.lastOpenedFile && this.lastOpenedFile.type !== 'tree') {
this.openPendingTab({
file: this.lastOpenedFile,
keyPrefix: this.lastOpenedFile.changed ? stageKeys.unstaged : stageKeys.staged,
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 08ee12fd98f..f9badb01535 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -87,7 +87,9 @@ export default {
this.editor.updateDimensions();
},
viewer() {
- this.createEditorInstance();
+ if (!this.file.pending) {
+ this.createEditorInstance();
+ }
},
panelResizing() {
if (!this.panelResizing) {
@@ -109,6 +111,7 @@ export default {
},
methods: {
...mapActions([
+ 'getFileData',
'getRawFileData',
'changeFileContent',
'setFileLanguage',
@@ -123,10 +126,16 @@ export default {
this.editor.clearEditor();
- this.getRawFileData({
+ this.getFileData({
path: this.file.path,
- baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
+ makeFileActive: false,
})
+ .then(() =>
+ this.getRawFileData({
+ path: this.file.path,
+ baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
+ }),
+ )
.then(() => {
this.createEditorInstance();
})
@@ -246,6 +255,8 @@ export default {
ref="editor"
:class="{
'is-readonly': isCommitModeActive,
+ 'is-deleted': file.deleted,
+ 'is-added': file.tempFile
}"
class="multi-file-editor-holder"
>
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index 3b4dd5ae9aa..eb4a927fe0d 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -34,11 +34,6 @@ export default {
type: Number,
required: true,
},
- disableActionDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
@@ -212,8 +207,7 @@ export default {
/>
</span>
<new-dropdown
- v-if="isTree && !disableActionDropdown"
- :project-id="file.projectId"
+ :type="file.type"
:branch="file.branchId"
:path="file.path"
:mouse-over="mouseOver"
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 03772ae4a4c..db47b75ec5c 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -37,7 +37,7 @@ export default {
return this.fileHasChanged ? !this.tabMouseOver : false;
},
fileHasChanged() {
- return this.tab.changed || this.tab.tempFile || this.tab.staged;
+ return this.tab.changed || this.tab.tempFile || this.tab.staged || this.tab.deleted;
},
},
@@ -71,7 +71,8 @@ export default {
<template>
<li
:class="{
- active: tab.active
+ active: tab.active,
+ disabled: tab.pending
}"
@click="clickFile(tab)"
@mouseover="mouseOverTab"
@@ -105,7 +106,6 @@ export default {
<changed-file-icon
v-else
:file="tab"
- :force-modified-icon="true"
/>
</button>
</li>
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 45d36f6f42c..0b514f31467 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -38,3 +38,18 @@ export const stageKeys = {
unstaged: 'unstaged',
staged: 'staged',
};
+
+export const commitItemIconMap = {
+ addition: {
+ icon: 'file-addition',
+ class: 'ide-file-addition',
+ },
+ modified: {
+ icon: 'file-modified',
+ class: 'ide-file-modified',
+ },
+ deleted: {
+ icon: 'file-deletion',
+ class: 'ide-file-deletion',
+ },
+};
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 78e6f632728..60bddb34977 100644
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -7,7 +7,7 @@ export default class Model {
this.disposable = new Disposable();
this.file = file;
this.head = head;
- this.content = file.content !== '' ? file.content : file.raw;
+ this.content = file.content !== '' || file.deleted ? file.content : file.raw;
this.disposable.add(
(this.originalModel = monacoEditor.createModel(
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index b5bd6f5a6bb..2765acada48 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -185,6 +185,14 @@ export const openNewEntryModal = ({ commit }, { type, path = '' }) => {
$('#ide-new-entry').modal('show');
};
+export const deleteEntry = ({ commit, dispatch, state }, path) => {
+ dispatch('burstUnusedSeal');
+ dispatch('closeFile', state.entries[path]);
+ commit(types.DELETE_ENTRY, path);
+};
+
+export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
+
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 6c0887e11ee..b343750f789 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -61,7 +61,11 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
const file = state.entries[path];
+
+ if (file.raw || file.tempFile) return Promise.resolve();
+
commit(types.TOGGLE_LOADING, { entry: file });
+
return service
.getFileData(
`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url.replace('/-/', '/')}`,
@@ -71,7 +75,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
setPageTitle(decodeURI(normalizedHeaders['PAGE-TITLE']));
commit(types.SET_FILE_DATA, { data, file });
- commit(types.TOGGLE_FILE_OPEN, path);
+ if (makeFileActive) commit(types.TOGGLE_FILE_OPEN, path);
if (makeFileActive) dispatch('setFileActive', path);
commit(types.TOGGLE_LOADING, { entry: file });
})
@@ -97,7 +101,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
service
.getRawFileData(file)
.then(raw => {
- commit(types.SET_FILE_RAW_DATA, { file, raw });
+ if (!file.tempFile) commit(types.SET_FILE_RAW_DATA, { file, raw });
if (file.mrChange && file.mrChange.new_file === false) {
service
.getBaseRawFileData(file, baseSha)
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index ffaaaabff17..acb6ef5e6d4 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -21,14 +21,12 @@ export const showTreeEntry = ({ commit, dispatch, state }, path) => {
export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
if (row.type === 'tree') {
dispatch('toggleTreeOpen', row.path);
- } else if (row.type === 'blob' && (row.opened || row.changed)) {
- if (row.changed && !row.opened) {
+ } else if (row.type === 'blob') {
+ if (!row.opened) {
commit(types.TOGGLE_FILE_OPEN, row.path);
}
dispatch('setFileActive', row.path);
- } else {
- dispatch('getFileData', { path: row.path });
}
dispatch('showTreeEntry', row.path);
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 7828c31f20e..462ca45db9b 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -174,11 +174,13 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
dispatch('updateActivityBarView', activityBarViews.edit, { root: true });
dispatch('updateViewer', 'editor', { root: true });
- router.push(
- `/project/${rootState.currentProjectId}/blob/${getters.branchName}/-/${
- rootGetters.activeFile.path
- }`,
- );
+ if (rootGetters.activeFile) {
+ router.push(
+ `/project/${rootState.currentProjectId}/blob/${getters.branchName}/-/${
+ rootGetters.activeFile.path
+ }`,
+ );
+ }
}
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH))
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 3db4b2f903e..03777e6c10b 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -1,7 +1,15 @@
-import { sprintf, n__ } from '../../../../locale';
+import { sprintf, n__, __ } from '../../../../locale';
import * as consts from './constants';
const BRANCH_SUFFIX_COUNT = 5;
+const createTranslatedTextForFiles = (files, text) => {
+ if (!files.length) return null;
+
+ return sprintf(n__('%{text} %{files}', '%{text} %{files} files', files.length), {
+ files: files.reduce((acc, val) => acc.concat(val.path), []).join(', '),
+ text,
+ });
+};
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
@@ -29,14 +37,16 @@ export const branchName = (state, getters, rootState) => {
export const preBuiltCommitMessage = (state, _, rootState) => {
if (state.commitMessage) return state.commitMessage;
- const files = (rootState.stagedFiles.length
- ? rootState.stagedFiles
- : rootState.changedFiles
- ).reduce((acc, val) => acc.concat(val.path), []);
+ const files = rootState.stagedFiles.length ? rootState.stagedFiles : rootState.changedFiles;
+ const modifiedFiles = files.filter(f => !f.deleted);
+ const deletedFiles = files.filter(f => f.deleted);
- return sprintf(n__('Update %{files}', 'Update %{files} files', files.length), {
- files: files.join(', '),
- });
+ return [
+ createTranslatedTextForFiles(modifiedFiles, __('Update')),
+ createTranslatedTextForFiles(deletedFiles, __('Deleted')),
+ ]
+ .filter(t => t)
+ .join('\n');
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index 8d6f9ccaf34..dae60f4d65a 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -76,3 +76,4 @@ export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL';
+export const DELETE_ENTRY = 'DELETE_ENTRY';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index f8091f5b5e0..799c2f51e8d 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
import projectMutations from './mutations/project';
import mergeRequestMutation from './mutations/merge_request';
@@ -171,6 +172,16 @@ export default {
newEntryModal: { type, path },
});
},
+ [types.DELETE_ENTRY](state, path) {
+ const entry = state.entries[path];
+ const parent = entry.parentPath
+ ? state.entries[entry.parentPath]
+ : state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
+
+ entry.deleted = true;
+ state.changedFiles = state.changedFiles.concat(entry);
+ parent.tree = parent.tree.filter(f => f.path !== entry.path);
+ },
...projectMutations,
...mergeRequestMutation,
...fileMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 46547820425..9a87d50d6d5 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */
import * as types from '../mutation_types';
+import { sortTree } from '../utils';
import { diffModes } from '../../constants';
export default {
@@ -51,9 +52,17 @@ export default {
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
+ const openPendingFile = state.openFiles.find(
+ f => f.path === file.path && f.pending && !f.tempFile,
+ );
+
Object.assign(state.entries[file.path], {
raw,
});
+
+ if (openPendingFile) {
+ openPendingFile.raw = raw;
+ }
},
[types.SET_FILE_BASE_RAW_DATA](state, { file, baseRaw }) {
Object.assign(state.entries[file.path], {
@@ -109,11 +118,22 @@ export default {
},
[types.DISCARD_FILE_CHANGES](state, path) {
const stagedFile = state.stagedFiles.find(f => f.path === path);
+ const entry = state.entries[path];
+ const { deleted } = entry;
Object.assign(state.entries[path], {
content: stagedFile ? stagedFile.content : state.entries[path].raw,
changed: false,
+ deleted: false,
});
+
+ if (deleted) {
+ const parent = entry.parentPath
+ ? state.entries[entry.parentPath]
+ : state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
+
+ parent.tree = sortTree(parent.tree.concat(entry));
+ }
},
[types.ADD_FILE_TO_CHANGED](state, path) {
Object.assign(state, {
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 9e6b86dd844..bf7ab93ff5e 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -46,6 +46,7 @@ export const dataStructure = () => ({
parentPath: null,
lastOpenedAt: 0,
mrChange: null,
+ deleted: false,
});
export const decorateData = entity => {
@@ -105,15 +106,37 @@ export const setPageTitle = title => {
document.title = title;
};
+export const commitActionForFile = file => {
+ if (file.deleted) {
+ return 'delete';
+ } else if (file.tempFile) {
+ return 'create';
+ }
+
+ return 'update';
+};
+
+export const getCommitFiles = (stagedFiles, deleteTree = false) =>
+ stagedFiles.reduce((acc, file) => {
+ if ((file.deleted || deleteTree) && file.type === 'tree') {
+ return acc.concat(getCommitFiles(file.tree, true));
+ }
+
+ return acc.concat({
+ ...file,
+ deleted: deleteTree || file.deleted,
+ });
+ }, []);
+
export const createCommitPayload = ({ branch, getters, newBranch, state, rootState }) => ({
branch,
commit_message: state.commitMessage || getters.preBuiltCommitMessage,
- actions: rootState.stagedFiles.map(f => ({
- action: f.tempFile ? 'create' : 'update',
+ actions: getCommitFiles(rootState.stagedFiles).map(f => ({
+ action: commitActionForFile(f),
file_path: f.path,
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
- last_commit_id: newBranch ? undefined : f.lastCommitSha,
+ last_commit_id: newBranch || f.deleted ? undefined : f.lastCommitSha,
})),
start_branch: newBranch ? rootState.currentBranchId : undefined,
});
diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js
new file mode 100644
index 00000000000..92b15cf232d
--- /dev/null
+++ b/app/assets/javascripts/ide/utils.js
@@ -0,0 +1,12 @@
+import { commitItemIconMap } from './constants';
+
+// eslint-disable-next-line import/prefer-default-export
+export const getCommitIconMap = file => {
+ if (file.deleted) {
+ return commitItemIconMap.deleted;
+ } else if (file.tempFile) {
+ return commitItemIconMap.addition;
+ }
+
+ return commitItemIconMap.modified;
+};
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 442a5e07a86..a346bd04ad3 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -77,6 +77,7 @@
.ide-file-icon-holder {
display: flex;
align-items: center;
+ color: $theme-gray-700;
}
.ide-file-changed-icon {
@@ -164,12 +165,23 @@
background-color: $white-light;
border-bottom-color: $white-light;
}
+
+ &:not(.disabled) {
+ .multi-file-tab {
+ cursor: pointer;
+ }
+ }
+
+ &.disabled {
+ .multi-file-tab-close {
+ cursor: default;
+ }
+ }
}
}
.multi-file-tab {
@include str-truncated(141px);
- cursor: pointer;
svg {
vertical-align: middle;
@@ -244,6 +256,38 @@
}
}
+ .is-deleted {
+ .editor.modified {
+ .margin-view-overlays,
+ .lines-content,
+ .decorationsOverviewRuler {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .diffOverviewRuler.modified {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .is-added {
+ .editor.original {
+ .margin-view-overlays,
+ .lines-content,
+ .decorationsOverviewRuler {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
+ .diffOverviewRuler.original {
+ // !important to override monaco inline styles
+ display: none !important;
+ }
+ }
+
.monaco-diff-editor.vs {
.editor.modified {
box-shadow: none;
@@ -560,16 +604,21 @@
}
}
-.multi-file-addition,
-.multi-file-addition-solid {
+.ide-file-addition,
+.ide-file-addition-solid {
color: $green-500;
}
-.multi-file-modified,
-.multi-file-modified-solid {
+.ide-file-modified,
+.ide-file-modified-solid {
color: $orange-500;
}
+.ide-file-deletion,
+.ide-file-deletion-solid {
+ color: $red-500;
+}
+
.multi-file-commit-list-collapsed {
display: flex;
flex-direction: column;
@@ -1017,6 +1066,10 @@
.ide-new-btn {
margin-left: auto;
}
+
+ button {
+ color: $gl-text-color;
+ }
}
.ide-sidebar-branch-title {
diff --git a/changelogs/unreleased/ide-delete-entries.yml b/changelogs/unreleased/ide-delete-entries.yml
new file mode 100644
index 00000000000..8cbc0739406
--- /dev/null
+++ b/changelogs/unreleased/ide-delete-entries.yml
@@ -0,0 +1,5 @@
+---
+title: Enabled deletion of files in the Web IDE
+merge_request:
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a5bbe8938ff..deeeea90dd0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -122,6 +122,11 @@ msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts
msgstr[0] ""
msgstr[1] ""
+msgid "%{text} %{files}"
+msgid_plural "%{text} %{files} files"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{text} is available"
msgstr ""
@@ -1992,6 +1997,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
+msgid "Deleted"
+msgstr ""
+
msgid "Deny"
msgstr ""
@@ -5537,10 +5545,8 @@ msgstr ""
msgid "Up to date"
msgstr ""
-msgid "Update %{files}"
-msgid_plural "Update %{files} files"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Update"
+msgstr ""
msgid "Update your group name, description, avatar, and other general settings."
msgstr ""
diff --git a/spec/javascripts/ide/components/changed_file_icon_spec.js b/spec/javascripts/ide/components/changed_file_icon_spec.js
index 541864e912e..7308219f705 100644
--- a/spec/javascripts/ide/components/changed_file_icon_spec.js
+++ b/spec/javascripts/ide/components/changed_file_icon_spec.js
@@ -33,14 +33,14 @@ describe('IDE changed file icon', () => {
});
describe('changedIconClass', () => {
- it('includes multi-file-modified when not a temp file', () => {
- expect(vm.changedIconClass).toContain('multi-file-modified');
+ it('includes ide-file-modified when not a temp file', () => {
+ expect(vm.changedIconClass).toContain('ide-file-modified');
});
- it('includes multi-file-addition when a temp file', () => {
+ it('includes ide-file-addition when a temp file', () => {
vm.file.tempFile = true;
- expect(vm.changedIconClass).toContain('multi-file-addition');
+ expect(vm.changedIconClass).toContain('ide-file-addition');
});
});
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
index bf96170f703..41d8bfff7e7 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
@@ -76,17 +76,29 @@ describe('Multi-file editor commit sidebar list item', () => {
expect(vm.iconName).toBe('file-addition');
});
+
+ it('returns deletion', () => {
+ f.deleted = true;
+
+ expect(vm.iconName).toBe('file-deletion');
+ });
});
describe('iconClass', () => {
it('returns modified when not a tempFile', () => {
- expect(vm.iconClass).toContain('multi-file-modified');
+ expect(vm.iconClass).toContain('ide-file-modified');
});
it('returns addition when not a tempFile', () => {
f.tempFile = true;
- expect(vm.iconClass).toContain('multi-file-addition');
+ expect(vm.iconClass).toContain('ide-file-addition');
+ });
+
+ it('returns deletion', () => {
+ f.deleted = true;
+
+ expect(vm.iconClass).toContain('ide-file-deletion');
});
});
});
diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js
index 4d704b80209..092c405a70b 100644
--- a/spec/javascripts/ide/components/new_dropdown/index_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js
@@ -14,6 +14,7 @@ describe('new dropdown component', () => {
branch: 'master',
path: '',
mouseOver: false,
+ type: 'tree',
});
vm.$store.state.currentProjectId = 'abcproject';
@@ -67,4 +68,14 @@ describe('new dropdown component', () => {
});
});
});
+
+ describe('delete entry', () => {
+ it('calls delete action', () => {
+ spyOn(vm, 'deleteEntry');
+
+ vm.$el.querySelectorAll('.dropdown-menu button')[3].click();
+
+ expect(vm.deleteEntry).toHaveBeenCalledWith('');
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 2256deb7dac..0e2e246defd 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
+import '~/behaviors/markdown/render_gfm';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
@@ -25,6 +26,8 @@ describe('RepoEditor', () => {
vm.$store.state.openFiles.push(f);
Vue.set(vm.$store.state.entries, f.path, f);
+ spyOn(vm, 'getFileData').and.returnValue(Promise.resolve());
+
vm.$mount();
Vue.nextTick(() => setTimeout(done));
diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js
index 156233653ab..f99d1f9890a 100644
--- a/spec/javascripts/ide/components/repo_file_spec.js
+++ b/spec/javascripts/ide/components/repo_file_spec.js
@@ -91,25 +91,6 @@ describe('RepoFile', () => {
done();
});
});
-
- it('disables action dropdown', done => {
- createComponent({
- file: {
- ...file('t4'),
- type: 'tree',
- branchId: 'master',
- projectId: 'project',
- },
- level: 0,
- disableActionDropdown: true,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.ide-new-btn')).toBeNull();
-
- done();
- });
- });
});
describe('locked file', () => {
diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/javascripts/ide/components/repo_tab_spec.js
index fc0695a4263..278a0753322 100644
--- a/spec/javascripts/ide/components/repo_tab_spec.js
+++ b/spec/javascripts/ide/components/repo_tab_spec.js
@@ -93,13 +93,13 @@ describe('RepoTab', () => {
Vue.nextTick()
.then(() => {
- expect(vm.$el.querySelector('.multi-file-modified')).toBeNull();
+ expect(vm.$el.querySelector('.ide-file-modified')).toBeNull();
vm.$el.dispatchEvent(new Event('mouseout'));
})
.then(Vue.nextTick)
.then(() => {
- expect(vm.$el.querySelector('.multi-file-modified')).not.toBeNull();
+ expect(vm.$el.querySelector('.ide-file-modified')).not.toBeNull();
done();
})
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 8b665a6d79e..792a716565c 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -7,6 +7,7 @@ import actions, {
updateActivityBarView,
updateTempFlagForEntry,
setErrorMessage,
+ deleteEntry,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types';
@@ -457,4 +458,19 @@ describe('Multi-file store actions', () => {
);
});
});
+
+ describe('deleteEntry', () => {
+ it('commits entry deletion', done => {
+ store.state.entries.path = 'testing';
+
+ testAction(
+ deleteEntry,
+ 'path',
+ store.state,
+ [{ type: types.DELETE_ENTRY, payload: 'path' }],
+ [{ type: 'burstUnusedSeal' }, { type: 'closeFile', payload: store.state.entries.path }],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 44c941d6dbb..3f4bf407a1f 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -123,6 +123,22 @@ describe('IDE commit module getters', () => {
'Update test-file, index.js files',
);
});
+
+ it('returns commitMessage with deleted files', () => {
+ rootState[key].push(
+ {
+ path: 'test-file',
+ deleted: true,
+ },
+ {
+ path: 'index.js',
+ },
+ );
+
+ expect(getters.preBuiltCommitMessage(state, null, rootState)).toBe(
+ 'Update index.js\nDeleted test-file',
+ );
+ });
});
});
});
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index 52f83be8e8c..efd0d86552b 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -94,6 +94,35 @@ describe('IDE store file mutations', () => {
expect(localFile.raw).toBe('testing');
});
+
+ it('adds raw data to open pending file', () => {
+ localState.openFiles.push({
+ ...localFile,
+ pending: true,
+ });
+
+ mutations.SET_FILE_RAW_DATA(localState, {
+ file: localFile,
+ raw: 'testing',
+ });
+
+ expect(localState.openFiles[0].raw).toBe('testing');
+ });
+
+ it('does not add raw data to open pending tempFile file', () => {
+ localState.openFiles.push({
+ ...localFile,
+ pending: true,
+ tempFile: true,
+ });
+
+ mutations.SET_FILE_RAW_DATA(localState, {
+ file: localFile,
+ raw: 'testing',
+ });
+
+ expect(localState.openFiles[0].raw).not.toBe('testing');
+ });
});
describe('SET_FILE_BASE_RAW_DATA', () => {
@@ -205,6 +234,11 @@ describe('IDE store file mutations', () => {
beforeEach(() => {
localFile.content = 'test';
localFile.changed = true;
+ localState.currentProjectId = 'gitlab-ce';
+ localState.currentBranchId = 'master';
+ localState.trees['gitlab-ce/master'] = {
+ tree: [],
+ };
});
it('resets content and changed', () => {
@@ -213,6 +247,36 @@ describe('IDE store file mutations', () => {
expect(localFile.content).toBe('');
expect(localFile.changed).toBeFalsy();
});
+
+ it('adds to root tree if deleted', () => {
+ localFile.deleted = true;
+
+ mutations.DISCARD_FILE_CHANGES(localState, localFile.path);
+
+ expect(localState.trees['gitlab-ce/master'].tree).toEqual([
+ {
+ ...localFile,
+ deleted: false,
+ },
+ ]);
+ });
+
+ it('adds to parent tree if deleted', () => {
+ localFile.deleted = true;
+ localFile.parentPath = 'parentPath';
+ localState.entries.parentPath = {
+ tree: [],
+ };
+
+ mutations.DISCARD_FILE_CHANGES(localState, localFile.path);
+
+ expect(localState.entries.parentPath.tree).toEqual([
+ {
+ ...localFile,
+ deleted: false,
+ },
+ ]);
+ });
});
describe('ADD_FILE_TO_CHANGED', () => {
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 98016f593aa..8b5f2d0bdfa 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -156,4 +156,61 @@ describe('Multi-file store mutations', () => {
expect(localState.errorMessage).toBe('error');
});
});
+
+ describe('DELETE_ENTRY', () => {
+ beforeEach(() => {
+ localState.currentProjectId = 'gitlab-ce';
+ localState.currentBranchId = 'master';
+ localState.trees['gitlab-ce/master'] = {
+ tree: [],
+ };
+ });
+
+ it('sets deleted flag', () => {
+ localState.entries.filePath = {
+ deleted: false,
+ };
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.entries.filePath.deleted).toBe(true);
+ });
+
+ it('removes from root tree', () => {
+ localState.entries.filePath = {
+ path: 'filePath',
+ deleted: false,
+ };
+ localState.trees['gitlab-ce/master'].tree.push(localState.entries.filePath);
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.trees['gitlab-ce/master'].tree).toEqual([]);
+ });
+
+ it('removes from parent tree', () => {
+ localState.entries.filePath = {
+ path: 'filePath',
+ deleted: false,
+ parentPath: 'parentPath',
+ };
+ localState.entries.parentPath = {
+ tree: [localState.entries.filePath],
+ };
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.entries.parentPath.tree).toEqual([]);
+ });
+
+ it('adds to changedFiles', () => {
+ localState.entries.filePath = {
+ deleted: false,
+ };
+
+ mutations.DELETE_ENTRY(localState, 'filePath');
+
+ expect(localState.changedFiles).toEqual([localState.entries.filePath]);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index 6c5980cfae4..89db50b8874 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -86,6 +86,11 @@ describe('Multi-file store utils', () => {
base64: true,
lastCommitSha: '123456789',
},
+ {
+ ...file('deletedFile'),
+ path: 'deletedFile',
+ deleted: true,
+ },
],
currentBranchId: 'master',
};
@@ -115,6 +120,13 @@ describe('Multi-file store utils', () => {
encoding: 'base64',
last_commit_id: '123456789',
},
+ {
+ action: 'delete',
+ file_path: 'deletedFile',
+ content: '',
+ encoding: 'text',
+ last_commit_id: undefined,
+ },
],
start_branch: undefined,
});
@@ -173,4 +185,65 @@ describe('Multi-file store utils', () => {
});
});
});
+
+ describe('commitActionForFile', () => {
+ it('returns deleted for deleted file', () => {
+ expect(utils.commitActionForFile({ deleted: true })).toBe('delete');
+ });
+
+ it('returns create for tempFile', () => {
+ expect(utils.commitActionForFile({ tempFile: true })).toBe('create');
+ });
+
+ it('returns update by default', () => {
+ expect(utils.commitActionForFile({})).toBe('update');
+ });
+ });
+
+ describe('getCommitFiles', () => {
+ it('returns flattened list of files and folders', () => {
+ const files = [
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'b',
+ type: 'tree',
+ deleted: true,
+ tree: [
+ {
+ path: 'c',
+ type: 'blob',
+ },
+ {
+ path: 'd',
+ type: 'blob',
+ },
+ ],
+ },
+ ];
+
+ const flattendFiles = utils.getCommitFiles(files);
+
+ expect(flattendFiles).toEqual([
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'd',
+ type: 'blob',
+ deleted: true,
+ },
+ ]);
+ });
+ });
});