summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/api.js14
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js1
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js2
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js2
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue20
-rw-r--r--app/assets/javascripts/clusters/services/clusters_service.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue7
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue7
-rw-r--r--app/assets/javascripts/dispatcher.js11
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js42
-rw-r--r--app/assets/javascripts/fly_out_nav.js17
-rw-r--r--app/assets/javascripts/helpers/user_feature_helper.js7
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue66
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue (renamed from app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue)0
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue (renamed from app/assets/javascripts/repo/components/commit_sidebar/list_item.vue)0
-rw-r--r--app/assets/javascripts/ide/components/ide.vue73
-rw-r--r--app/assets/javascripts/ide/components/ide_context_bar.vue75
-rw-r--r--app/assets/javascripts/ide/components/ide_project_branches_tree.vue47
-rw-r--r--app/assets/javascripts/ide/components/ide_project_tree.vue47
-rw-r--r--app/assets/javascripts/ide/components/ide_repo_tree.vue66
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue62
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue71
-rw-r--r--app/assets/javascripts/ide/components/new_branch_form.vue (renamed from app/assets/javascripts/repo/components/new_branch_form.vue)2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue101
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue (renamed from app/assets/javascripts/repo/components/new_dropdown/modal.vue)16
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue (renamed from app/assets/javascripts/repo/components/new_dropdown/upload.vue)35
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue (renamed from app/assets/javascripts/repo/components/repo_commit_section.vue)45
-rw-r--r--app/assets/javascripts/ide/components/repo_edit_button.vue (renamed from app/assets/javascripts/repo/components/repo_edit_button.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue (renamed from app/assets/javascripts/repo/components/repo_editor.vue)42
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue (renamed from app/assets/javascripts/repo/components/repo_file.vue)74
-rw-r--r--app/assets/javascripts/ide/components/repo_file_buttons.vue (renamed from app/assets/javascripts/repo/components/repo_file_buttons.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue (renamed from app/assets/javascripts/repo/components/repo_loading_file.vue)8
-rw-r--r--app/assets/javascripts/ide/components/repo_prev_directory.vue (renamed from app/assets/javascripts/repo/components/repo_prev_directory.vue)8
-rw-r--r--app/assets/javascripts/ide/components/repo_preview.vue (renamed from app/assets/javascripts/repo/components/repo_preview.vue)0
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue (renamed from app/assets/javascripts/repo/components/repo_tab.vue)6
-rw-r--r--app/assets/javascripts/ide/components/repo_tabs.vue (renamed from app/assets/javascripts/repo/components/repo_tabs.vue)0
-rw-r--r--app/assets/javascripts/ide/ide_router.js101
-rw-r--r--app/assets/javascripts/ide/index.js55
-rw-r--r--app/assets/javascripts/ide/lib/common/disposable.js (renamed from app/assets/javascripts/repo/lib/common/disposable.js)0
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js (renamed from app/assets/javascripts/repo/lib/common/model.js)8
-rw-r--r--app/assets/javascripts/ide/lib/common/model_manager.js (renamed from app/assets/javascripts/repo/lib/common/model_manager.js)0
-rw-r--r--app/assets/javascripts/ide/lib/decorations/controller.js (renamed from app/assets/javascripts/repo/lib/decorations/controller.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/controller.js (renamed from app/assets/javascripts/repo/lib/diff/controller.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff.js (renamed from app/assets/javascripts/repo/lib/diff/diff.js)0
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff_worker.js (renamed from app/assets/javascripts/repo/lib/diff/diff_worker.js)0
-rw-r--r--app/assets/javascripts/ide/lib/editor.js (renamed from app/assets/javascripts/repo/lib/editor.js)30
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js (renamed from app/assets/javascripts/repo/lib/editor_options.js)0
-rw-r--r--app/assets/javascripts/ide/monaco_loader.js (renamed from app/assets/javascripts/repo/monaco_loader.js)0
-rw-r--r--app/assets/javascripts/ide/services/index.js (renamed from app/assets/javascripts/repo/services/index.js)7
-rw-r--r--app/assets/javascripts/ide/stores/actions.js179
-rw-r--r--app/assets/javascripts/ide/stores/actions/branch.js43
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js (renamed from app/assets/javascripts/repo/stores/actions/file.js)41
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js25
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js188
-rw-r--r--app/assets/javascripts/ide/stores/getters.js19
-rw-r--r--app/assets/javascripts/ide/stores/index.js (renamed from app/assets/javascripts/repo/stores/index.js)0
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js (renamed from app/assets/javascripts/repo/stores/mutation_types.js)19
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js (renamed from app/assets/javascripts/repo/stores/mutations.js)18
-rw-r--r--app/assets/javascripts/ide/stores/mutations/branch.js28
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js (renamed from app/assets/javascripts/repo/stores/mutations/file.js)20
-rw-r--r--app/assets/javascripts/ide/stores/mutations/project.js23
-rw-r--r--app/assets/javascripts/ide/stores/mutations/tree.js (renamed from app/assets/javascripts/repo/stores/mutations/tree.js)9
-rw-r--r--app/assets/javascripts/ide/stores/state.js (renamed from app/assets/javascripts/repo/stores/state.js)20
-rw-r--r--app/assets/javascripts/ide/stores/utils.js (renamed from app/assets/javascripts/repo/stores/utils.js)64
-rw-r--r--app/assets/javascripts/init_issuable_sidebar.js3
-rw-r--r--app/assets/javascripts/init_legacy_filters.js2
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js3
-rw-r--r--app/assets/javascripts/job.js4
-rw-r--r--app/assets/javascripts/milestone_select.js421
-rw-r--r--app/assets/javascripts/new_commit_form.js7
-rw-r--r--app/assets/javascripts/pages/users/show/index.js3
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue11
-rw-r--r--app/assets/javascripts/repo/components/commit_sidebar/list.vue89
-rw-r--r--app/assets/javascripts/repo/components/new_dropdown/index.vue89
-rw-r--r--app/assets/javascripts/repo/components/repo.vue63
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue85
-rw-r--r--app/assets/javascripts/repo/index.js106
-rw-r--r--app/assets/javascripts/repo/stores/actions.js146
-rw-r--r--app/assets/javascripts/repo/stores/actions/branch.js20
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js163
-rw-r--r--app/assets/javascripts/repo/stores/getters.js40
-rw-r--r--app/assets/javascripts/repo/stores/mutations/branch.js9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js11
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue103
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue8
88 files changed, 2090 insertions, 1193 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index d963101028a..21d8c790e90 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
@@ -6,6 +7,7 @@ const Api = {
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
+ projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels',
groupLabelsPath: '/groups/:namespace_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
@@ -76,6 +78,14 @@ const Api = {
.done(projects => callback(projects));
},
+ // Return single project
+ project(projectPath) {
+ const url = Api.buildUrl(Api.projectPath)
+ .replace(':id', encodeURIComponent(projectPath));
+
+ return axios.get(url);
+ },
+
newLabel(namespacePath, projectPath, data, callback) {
let url;
@@ -115,7 +125,7 @@ const Api = {
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
- .replace(':id', id);
+ .replace(':id', encodeURIComponent(id));
return this.wrapAjaxCall({
url,
type: 'POST',
@@ -127,7 +137,7 @@ const Api = {
branchSingle(id, branch) {
const url = Api.buildUrl(Api.branchSinglePath)
- .replace(':id', id)
+ .replace(':id', encodeURIComponent(id))
.replace(':branch', branch);
return this.wrapAjaxCall({
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 20d23162940..0c1cff1da7a 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -102,7 +102,6 @@ $(() => {
if (list.type === 'closed') {
list.position = Infinity;
- list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' };
} else if (list.type === 'backlog') {
list.position = -1;
}
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 616de2347e1..983429550f0 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -1,5 +1,4 @@
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
-/* global MilestoneSelect */
import Vue from 'vue';
import Flash from '../../flash';
@@ -12,6 +11,7 @@ import './sidebar/remove_issue';
import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select';
import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
+import MilestoneSelect from '../../milestone_select';
const Store = gl.issueBoards.BoardsStore;
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 2cfd6179a25..637d0dbde23 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -30,6 +30,7 @@ export default class Clusters {
installHelmPath,
installIngressPath,
installRunnerPath,
+ installPrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
@@ -44,6 +45,7 @@ export default class Clusters {
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
installRunnerEndpoint: installRunnerPath,
+ installPrometheusEndpoint: installPrometheusPath,
});
this.toggle = this.toggle.bind(this);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index e5ae439d26e..cd58b88db69 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -67,6 +67,16 @@ export default {
and send the results back to GitLab.`,
));
},
+ prometheusDescription() {
+ return sprintf(
+ _.escape(s__('ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications.')), {
+ gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html", target="_blank" rel="noopener noreferrer">
+ ${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
+ </a>`,
+ },
+ false,
+ );
+ },
},
};
</script>
@@ -106,6 +116,16 @@ export default {
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
/>
+ <application-row
+ id="prometheus"
+ :title="applications.prometheus.title"
+ title-link="https://prometheus.io/docs/introduction/overview/"
+ :description="prometheusDescription"
+ :status="applications.prometheus.status"
+ :status-reason="applications.prometheus.statusReason"
+ :request-status="applications.prometheus.requestStatus"
+ :request-reason="applications.prometheus.requestReason"
+ />
<!-- NOTE: Don't forget to update `clusters.scss` min-height for this block and uncomment `application_spec` tests -->
<!-- Add GitLab Runner row, all other plumbing is complete -->
</div>
diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js
index 755c2981c2e..13468578f4f 100644
--- a/app/assets/javascripts/clusters/services/clusters_service.js
+++ b/app/assets/javascripts/clusters/services/clusters_service.js
@@ -7,6 +7,7 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
runner: this.options.installRunnerEndpoint,
+ prometheus: this.options.installPrometheusEndpoint,
};
}
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index e731cdc3042..bd4a1fb37f9 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -28,6 +28,13 @@ export default class ClusterStore {
requestStatus: null,
requestReason: null,
},
+ prometheus: {
+ title: s__('ClusterIntegration|Prometheus'),
+ status: null,
+ statusReason: null,
+ requestStatus: null,
+ requestReason: null,
+ },
},
};
}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
index f54ea7df522..cbce9205e75 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -2,6 +2,7 @@
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -12,6 +13,7 @@
userAvatarImage,
totalTime,
limitWarning,
+ icon,
},
};
</script>
@@ -52,7 +54,10 @@
</template>
<template v-else>
<span class="merge-request-branch" v-if="mergeRequest.branch">
- <i class= "fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
</span>
</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
index 5d95ddcd90e..508a411e599 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -13,6 +14,7 @@
userAvatarImage,
totalTime,
limitWarning,
+ icon,
},
computed: {
iconBranch() {
@@ -37,7 +39,10 @@
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
index 04d5440b77b..88fa6b073ca 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -3,6 +3,7 @@
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -12,6 +13,7 @@
components: {
totalTime,
limitWarning,
+ icon,
},
computed: {
iconBuildStatus() {
@@ -40,7 +42,10 @@
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
&middot;
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
+ <icon
+ name="fork"
+ :size="16">
+ </icon>
<a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
<span class="icon-branch" v-html="iconBranch"></span>
<a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 62867c56214..42f61d33f6e 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -5,7 +5,7 @@ import IssuableIndex from './issuable_index';
import Milestone from './milestone';
import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select';
-/* global MilestoneSelect */
+import MilestoneSelect from './milestone_select';
import NewBranchForm from './new_branch_form';
import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
@@ -73,7 +73,6 @@ import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges';
-import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown';
import NewGroupChild from './groups/new_group_child';
import AbuseReports from './abuse_reports';
@@ -111,6 +110,8 @@ import Activities from './activities';
return false;
}
+ const fail = () => Flash('Error loading dynamic module');
+
path = page.split(':');
shortcut_handler = null;
@@ -447,9 +448,6 @@ import Activities from './activities';
break;
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
-
- if (UserFeatureHelper.isNewRepoEnabled()) break;
-
new TreeView();
new BlobViewer();
new NewCommitForm($('.js-create-dir-form'));
@@ -468,7 +466,6 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'projects:blob:show':
- if (UserFeatureHelper.isNewRepoEnabled()) break;
new BlobViewer();
initBlob();
break;
@@ -545,7 +542,7 @@ import Activities from './activities';
new CILintEditor();
break;
case 'users:show':
- new UserCallout();
+ import('./pages/users/show').then(m => m.default()).catch(fail);
break;
case 'admin:conversational_development_index:show':
new UserCallout();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 2ba85c7da97..c05a83176f2 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -127,7 +127,7 @@ class FilteredSearchManager {
this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
- this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
+ this.checkForBackspaceWrapper = this.checkForBackspace.call(this);
this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
@@ -180,22 +180,34 @@ class FilteredSearchManager {
this.unbindStateEvents();
}
- checkForBackspace(e) {
- // 8 = Backspace Key
- // 46 = Delete Key
- if (e.keyCode === 8 || e.keyCode === 46) {
- const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ checkForBackspace() {
+ let backspaceCount = 0;
+
+ // closure for keeping track of the number of backspace keystrokes
+ return (e) => {
+ // 8 = Backspace Key
+ // 46 = Delete Key
+ if (e.keyCode === 8 || e.keyCode === 46) {
+ const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
+ const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
+
+ if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
+ backspaceCount += 1;
+
+ if (backspaceCount === 2) {
+ backspaceCount = 0;
+ this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ }
+ }
- const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
- const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
- if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
- this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
- gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ // Reposition dropdown so that it is aligned with cursor
+ this.dropdownManager.updateCurrentDropdownOffset();
+ } else {
+ backspaceCount = 0;
}
-
- // Reposition dropdown so that it is aligned with cursor
- this.dropdownManager.updateCurrentDropdownOffset();
- }
+ };
}
checkForEnter(e) {
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 6110d961609..abb04d77f8f 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -161,13 +161,16 @@ export default () => {
const items = [...sidebar.querySelectorAll('.sidebar-top-level-items > li')];
- sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => {
- clearTimeout(timeoutId);
-
- timeoutId = setTimeout(() => {
- if (currentOpenMenu) hideMenu(currentOpenMenu);
- }, getHideSubItemsInterval());
- });
+ const topItems = sidebar.querySelector('.sidebar-top-level-items');
+ if (topItems) {
+ sidebar.querySelector('.sidebar-top-level-items').addEventListener('mouseleave', () => {
+ clearTimeout(timeoutId);
+
+ timeoutId = setTimeout(() => {
+ if (currentOpenMenu) hideMenu(currentOpenMenu);
+ }, getHideSubItemsInterval());
+ });
+ }
headerHeight = document.querySelector('.nav-sidebar').offsetTop;
diff --git a/app/assets/javascripts/helpers/user_feature_helper.js b/app/assets/javascripts/helpers/user_feature_helper.js
deleted file mode 100644
index 638118a5204..00000000000
--- a/app/assets/javascripts/helpers/user_feature_helper.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Cookies from 'js-cookie';
-
-export default {
- isNewRepoEnabled() {
- return Cookies.get('new_repo') === 'true';
- },
-};
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
new file mode 100644
index 00000000000..704dff981df
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -0,0 +1,66 @@
+<script>
+ import { mapState } from 'vuex';
+ import icon from '../../../vue_shared/components/icon.vue';
+ import listItem from './list_item.vue';
+ import listCollapsed from './list_collapsed.vue';
+
+ export default {
+ components: {
+ icon,
+ listItem,
+ listCollapsed,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ fileList: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'currentProjectId',
+ 'currentBranchId',
+ 'rightPanelCollapsed',
+ ]),
+ },
+ methods: {
+ toggleCollapsed() {
+ this.$emit('toggleCollapsed');
+ },
+ },
+
+ };
+</script>
+
+<template>
+ <div class="multi-file-commit-list">
+ <list-collapsed
+ v-if="rightPanelCollapsed"
+ />
+ <template v-else>
+ <ul
+ v-if="fileList.length"
+ class="list-unstyled append-bottom-0"
+ >
+ <li
+ v-for="file in fileList"
+ :key="file.key"
+ >
+ <list-item
+ :file="file"
+ />
+ </li>
+ </ul>
+ <div
+ v-else
+ class="help-block prepend-top-0"
+ >
+ No changes
+ </div>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index 6a0262f271b..6a0262f271b 100644
--- a/app/assets/javascripts/repo/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 742f746e02f..742f746e02f 100644
--- a/app/assets/javascripts/repo/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
new file mode 100644
index 00000000000..7f29a355eca
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -0,0 +1,73 @@
+<script>
+import { mapState, mapGetters } from 'vuex';
+import ideSidebar from './ide_side_bar.vue';
+import ideContextbar from './ide_context_bar.vue';
+import repoTabs from './repo_tabs.vue';
+import repoFileButtons from './repo_file_buttons.vue';
+import ideStatusBar from './ide_status_bar.vue';
+import repoPreview from './repo_preview.vue';
+import repoEditor from './repo_editor.vue';
+
+export default {
+ computed: {
+ ...mapState([
+ 'currentBlobView',
+ 'selectedFile',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ 'activeFile',
+ ]),
+ },
+ components: {
+ ideSidebar,
+ ideContextbar,
+ repoTabs,
+ repoFileButtons,
+ ideStatusBar,
+ repoEditor,
+ repoPreview,
+ },
+ mounted() {
+ const returnValue = 'Are you sure you want to lose unsaved changes?';
+ window.onbeforeunload = (e) => {
+ if (!this.changedFiles.length) return undefined;
+
+ Object.assign(e, {
+ returnValue,
+ });
+ return returnValue;
+ };
+ },
+};
+</script>
+
+<template>
+ <div
+ class="ide-view"
+ >
+ <ide-sidebar/>
+ <div
+ class="multi-file-edit-pane"
+ >
+ <template
+ v-if="activeFile">
+ <repo-tabs/>
+ <component
+ class="multi-file-edit-pane-content"
+ :is="currentBlobView"
+ />
+ <repo-file-buttons/>
+ <ide-status-bar
+ :file="selectedFile"/>
+ </template>
+ <template
+ v-else>
+ <div class="ide-empty-state">
+ <h2 class="clgray">Welcome to the GitLab IDE</h2>
+ </div>
+ </template>
+ </div>
+ <ide-contextbar/>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
new file mode 100644
index 00000000000..5a08718e386
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_context_bar.vue
@@ -0,0 +1,75 @@
+<script>
+import { mapGetters, mapState, mapActions } from 'vuex';
+import repoCommitSection from './repo_commit_section.vue';
+import icon from '../../vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ repoCommitSection,
+ icon,
+ },
+ computed: {
+ ...mapState([
+ 'rightPanelCollapsed',
+ ]),
+ ...mapGetters([
+ 'changedFiles',
+ ]),
+ currentIcon() {
+ return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'right',
+ collapsed: !this.rightPanelCollapsed,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="multi-file-commit-panel"
+ :class="{
+ 'is-collapsed': rightPanelCollapsed,
+ }"
+ >
+ <div
+ class="multi-file-commit-panel-section">
+ <header
+ class="multi-file-commit-panel-header"
+ :class="{
+ 'is-collapsed': rightPanelCollapsed,
+ }"
+ >
+ <div
+ class="multi-file-commit-panel-header-title"
+ v-if="!rightPanelCollapsed">
+ <icon
+ name="list-bulleted"
+ :size="18"
+ />
+ Staged
+ </div>
+ <button
+ type="button"
+ class="btn btn-transparent multi-file-commit-panel-collapse-btn"
+ @click="toggleCollapsed"
+ >
+ <icon
+ :name="currentIcon"
+ :size="18"
+ />
+ </button>
+ </header>
+ <repo-commit-section
+ class=""/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
new file mode 100644
index 00000000000..bd3a521ff43
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
@@ -0,0 +1,47 @@
+<script>
+import repoTree from './ide_repo_tree.vue';
+import icon from '../../vue_shared/components/icon.vue';
+import newDropdown from './new_dropdown/index.vue';
+
+export default {
+ components: {
+ repoTree,
+ icon,
+ newDropdown,
+ },
+ props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
+ branch: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="branch-container">
+ <div class="branch-header">
+ <div class="branch-header-title">
+ <icon
+ name="branch"
+ :size="12">
+ </icon>
+ {{ branch.name }}
+ </div>
+ <div class="branch-header-btns">
+ <new-dropdown
+ :project-id="projectId"
+ :branch="branch.name"
+ path=""/>
+ </div>
+ </div>
+ <div>
+ <repo-tree
+ :treeId="branch.treeId"/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
new file mode 100644
index 00000000000..61daba6d176
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_project_tree.vue
@@ -0,0 +1,47 @@
+<script>
+import branchesTree from './ide_project_branches_tree.vue';
+import projectAvatarImage from '../../vue_shared/components/project_avatar/image.vue';
+
+export default {
+ components: {
+ branchesTree,
+ projectAvatarImage,
+ },
+ props: {
+ project: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="projects-sidebar">
+ <div class="context-header">
+ <a
+ :title="project.name"
+ :href="project.web_url">
+ <div class="avatar-container s40 project-avatar">
+ <project-avatar-image
+ class="avatar-container project-avatar"
+ :link-href="project.path"
+ :img-src="project.avatar_url"
+ :img-alt="project.name"
+ :img-size="40"
+ />
+ </div>
+ <div class="sidebar-context-title">
+ {{ project.name }}
+ </div>
+ </a>
+ </div>
+ <div class="multi-file-commit-panel-inner-scroll">
+ <branches-tree
+ v-for="(branch, index) in project.branches"
+ :key="branch.name"
+ :project-id="project.path_with_namespace"
+ :branch="branch"/>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
new file mode 100644
index 00000000000..b6b089e6b25
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue
@@ -0,0 +1,66 @@
+<script>
+import { mapState } from 'vuex';
+import RepoPreviousDirectory from './repo_prev_directory.vue';
+import RepoFile from './repo_file.vue';
+import RepoLoadingFile from './repo_loading_file.vue';
+import { treeList } from '../stores/utils';
+
+export default {
+ components: {
+ 'repo-previous-directory': RepoPreviousDirectory,
+ 'repo-file': RepoFile,
+ 'repo-loading-file': RepoLoadingFile,
+ },
+ props: {
+ treeId: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'loading',
+ 'isRoot',
+ ]),
+ ...mapState({
+ projectName(state) {
+ return state.project.name;
+ },
+ }),
+ fetchedList() {
+ return treeList(this.$store.state, this.treeId);
+ },
+ hasPreviousDirectory() {
+ return !this.isRoot && this.fetchedList.length;
+ },
+ showLoading() {
+ return this.loading;
+ },
+ },
+};
+</script>
+
+<template>
+<div>
+ <div class="ide-file-list">
+ <table class="table">
+ <tbody
+ v-if="treeId">
+ <repo-previous-directory
+ v-if="hasPreviousDirectory"
+ />
+ <repo-loading-file
+ v-if="showLoading"
+ v-for="n in 5"
+ :key="n"
+ />
+ <repo-file
+ v-for="file in fetchedList"
+ :key="file.key"
+ :file="file"
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
new file mode 100644
index 00000000000..535398d98c2
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -0,0 +1,62 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import projectTree from './ide_project_tree.vue';
+import icon from '../../vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ projectTree,
+ icon,
+ },
+ computed: {
+ ...mapState([
+ 'projects',
+ 'leftPanelCollapsed',
+ ]),
+ currentIcon() {
+ return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
+ },
+ },
+ methods: {
+ ...mapActions([
+ 'setPanelCollapsedStatus',
+ ]),
+ toggleCollapsed() {
+ this.setPanelCollapsedStatus({
+ side: 'left',
+ collapsed: !this.leftPanelCollapsed,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="multi-file-commit-panel"
+ :class="{
+ 'is-collapsed': leftPanelCollapsed,
+ }"
+ >
+ <div class="multi-file-commit-panel-inner">
+ <project-tree
+ v-for="(project, index) in projects"
+ :key="project.id"
+ :project="project"/>
+ </div>
+ <button
+ type="button"
+ class="btn btn-transparent left-collapse-btn"
+ @click="toggleCollapsed"
+ >
+ <icon
+ :name="currentIcon"
+ :size="18"
+ />
+ <span
+ v-if="!leftPanelCollapsed"
+ class="collapse-text"
+ >Collapse sidebar</span>
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
new file mode 100644
index 00000000000..a24abadd936
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -0,0 +1,71 @@
+<script>
+import { mapState } from 'vuex';
+import icon from '../../vue_shared/components/icon.vue';
+import tooltip from '../../vue_shared/directives/tooltip';
+import timeAgoMixin from '../../vue_shared/mixins/timeago';
+
+export default {
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeAgoMixin,
+ ],
+ computed: {
+ ...mapState([
+ 'selectedFile',
+ ]),
+ },
+};
+</script>
+
+<template>
+ <div
+ class="ide-status-bar">
+ <div>
+ <icon
+ name="branch"
+ :size="12">
+ </icon>
+ {{ selectedFile.branchId }}
+ </div>
+ <div>
+ <div
+ v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
+ Last commit:
+ <a
+ v-tooltip
+ :title="selectedFile.lastCommit.message"
+ :href="selectedFile.lastCommit.url">
+ {{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
+ {{ selectedFile.lastCommit.author }}
+ </a>
+ </div>
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.name }}
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.eol }}
+ </div>
+ <div
+ class="text-right">
+ {{ file.editorRow }}:{{ file.editorColumn }}
+ </div>
+ <div
+ class="text-right">
+ {{ selectedFile.fileLanguage }}
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
index ba7090e4a9d..2119d373d31 100644
--- a/app/assets/javascripts/repo/components/new_branch_form.vue
+++ b/app/assets/javascripts/ide/components/new_branch_form.vue
@@ -44,7 +44,7 @@
this.branchName = '';
if (this.dropdownText) {
- this.dropdownText.textContent = this.currentBranch;
+ this.dropdownText.textContent = this.currentBranchId;
}
this.toggleDropdown();
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
new file mode 100644
index 00000000000..6e67e99a70f
--- /dev/null
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -0,0 +1,101 @@
+<script>
+ import newModal from './modal.vue';
+ import upload from './upload.vue';
+ import icon from '../../../vue_shared/components/icon.vue';
+
+ export default {
+ props: {
+ branch: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ parent: {
+ type: Object,
+ default: null,
+ },
+ },
+ components: {
+ icon,
+ newModal,
+ upload,
+ },
+ data() {
+ return {
+ openModal: false,
+ modalType: '',
+ };
+ },
+ methods: {
+ createNewItem(type) {
+ this.modalType = type;
+ this.toggleModalOpen();
+ },
+ toggleModalOpen() {
+ this.openModal = !this.openModal;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="repo-new-btn pull-right">
+ <div class="dropdown">
+ <button
+ type="button"
+ class="btn btn-sm btn-default dropdown-toggle add-to-tree"
+ data-toggle="dropdown"
+ aria-label="Create new file or directory"
+ >
+ <icon
+ name="plus"
+ :size="12"
+ css-classes="pull-left"
+ />
+ <icon
+ name="arrow-down"
+ :size="12"
+ css-classes="pull-left"
+ />
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="createNewItem('blob')"
+ >
+ {{ __('New file') }}
+ </a>
+ </li>
+ <li>
+ <upload
+ :branch-id="branch"
+ :path="path"
+ :parent="parent"
+ />
+ </li>
+ <li>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="createNewItem('tree')"
+ >
+ {{ __('New directory') }}
+ </a>
+ </li>
+ </ul>
+ </div>
+ <new-modal
+ v-if="openModal"
+ :type="modalType"
+ :branch-id="branch"
+ :path="path"
+ :parent="parent"
+ @toggle="toggleModalOpen"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index c191af7dec3..a0650d37690 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,10 +1,18 @@
<script>
- import { mapActions } from 'vuex';
+ import { mapActions, mapState } from 'vuex';
import { __ } from '../../../locale';
import modal from '../../../vue_shared/components/modal.vue';
export default {
props: {
+ branchId: {
+ type: String,
+ required: true,
+ },
+ parent: {
+ type: Object,
+ default: null,
+ },
type: {
type: String,
required: true,
@@ -28,6 +36,9 @@
]),
createEntryInStore() {
this.createTempEntry({
+ projectId: this.currentProjectId,
+ branchId: this.branchId,
+ parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
type: this.type,
});
@@ -39,6 +50,9 @@
},
},
computed: {
+ ...mapState([
+ 'currentProjectId',
+ ]),
modalTitle() {
if (this.type === 'tree') {
return __('Create new directory');
diff --git a/app/assets/javascripts/repo/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index 14ad32f4ae0..2a2f2a241fc 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -1,12 +1,22 @@
<script>
- import { mapActions } from 'vuex';
+ import { mapActions, mapState } from 'vuex';
export default {
props: {
- path: {
+ branchId: {
type: String,
required: true,
},
+ parent: {
+ type: Object,
+ default: null,
+ },
+ },
+ computed: {
+ ...mapState([
+ 'trees',
+ 'currentProjectId',
+ ]),
},
methods: {
...mapActions([
@@ -22,6 +32,9 @@
this.createTempEntry({
name,
+ projectId: this.currentProjectId,
+ branchId: this.branchId,
+ parent: this.parent,
type: 'blob',
content: result,
base64: !isText,
@@ -42,6 +55,9 @@
openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
},
+ startFileUpload() {
+ this.$refs.fileUpload.click();
+ },
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
@@ -53,16 +69,19 @@
</script>
<template>
- <label
- role="button"
- class="menu-item"
- >
- {{ __('Upload file') }}
+ <div>
+ <a
+ href="#"
+ role="button"
+ @click.prevent="startFileUpload"
+ >
+ {{ __('Upload file') }}
+ </a>
<input
id="file-upload"
type="file"
class="hidden"
ref="fileUpload"
/>
- </label>
+ </div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 4e0178072cb..470db2c9650 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -20,12 +20,13 @@ export default {
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
- collapsed: true,
};
},
computed: {
...mapState([
- 'currentBranch',
+ 'currentProjectId',
+ 'currentBranchId',
+ 'rightPanelCollapsed',
]),
...mapGetters([
'changedFiles',
@@ -42,12 +43,13 @@ export default {
'checkCommitStatus',
'commitChanges',
'getTreeData',
+ 'setPanelCollapsedStatus',
]),
makeCommit(newBranch = false) {
const createNewBranch = newBranch || this.startNewMR;
const payload = {
- branch: createNewBranch ? `${this.currentBranch}-${new Date().getTime().toString()}` : this.currentBranch,
+ branch: createNewBranch ? `${this.currentBranchId}-${new Date().getTime().toString()}` : this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
@@ -55,7 +57,7 @@ export default {
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
})),
- start_branch: createNewBranch ? this.currentBranch : undefined,
+ start_branch: createNewBranch ? this.currentBranchId : undefined,
};
this.showNewBranchModal = false;
@@ -64,7 +66,12 @@ export default {
this.commitChanges({ payload, newMr: this.startNewMR })
.then(() => {
this.submitCommitsLoading = false;
- this.getTreeData();
+ this.$store.dispatch('getTreeData', {
+ projectId: this.currentProjectId,
+ branch: this.currentBranchId,
+ endpoint: `/tree/${this.currentBranchId}`,
+ force: true,
+ });
})
.catch(() => {
this.submitCommitsLoading = false;
@@ -86,19 +93,17 @@ export default {
});
},
toggleCollapsed() {
- this.collapsed = !this.collapsed;
+ this.setPanelCollapsedStatus({
+ side: 'right',
+ collapsed: !this.rightPanelCollapsed,
+ });
},
},
};
</script>
<template>
-<div
- class="multi-file-commit-panel"
- :class="{
- 'is-collapsed': collapsed,
- }"
->
+<div class="multi-file-commit-panel-section">
<modal
v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
@@ -108,28 +113,16 @@ export default {
@toggle="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
- <button
- v-if="collapsed"
- type="button"
- class="btn btn-transparent multi-file-commit-panel-collapse-btn is-collapsed prepend-top-10 append-bottom-10"
- @click="toggleCollapsed"
- >
- <i
- aria-hidden="true"
- class="fa fa-angle-double-left"
- >
- </i>
- </button>
<commit-files-list
title="Staged"
:file-list="changedFiles"
- :collapsed="collapsed"
+ :collapsed="rightPanelCollapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form
class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit"
- v-if="!collapsed"
+ v-if="!rightPanelCollapsed"
>
<div class="multi-file-commit-fieldset">
<textarea
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
index 37bd9003e96..37bd9003e96 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/ide/components/repo_edit_button.vue
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f37cbd1e961..221be4b9074 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -1,6 +1,6 @@
<script>
/* global monaco */
-import { mapGetters, mapActions } from 'vuex';
+import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '../../flash';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
@@ -24,6 +24,9 @@ export default {
...mapActions([
'getRawFileData',
'changeFileContent',
+ 'setFileLanguage',
+ 'setEditorPosition',
+ 'setFileEOL',
]),
initMonaco() {
if (this.shouldHideEditor) return;
@@ -43,12 +46,36 @@ export default {
const model = this.editor.createModel(this.activeFile);
this.editor.attachModel(model);
+
model.onChange((m) => {
this.changeFileContent({
file: this.activeFile,
content: m.getValue(),
});
});
+
+ // Handle Cursor Position
+ this.editor.onPositionChange((instance, e) => {
+ this.setEditorPosition({
+ editorRow: e.position.lineNumber,
+ editorColumn: e.position.column,
+ });
+ });
+
+ this.editor.setPosition({
+ lineNumber: this.activeFile.editorRow,
+ column: this.activeFile.editorColumn,
+ });
+
+ // Handle File Language
+ this.setFileLanguage({
+ fileLanguage: model.language,
+ });
+
+ // Get File eol
+ this.setFileEOL({
+ eol: model.eol,
+ });
},
},
watch: {
@@ -57,12 +84,22 @@ export default {
this.initMonaco();
}
},
+ leftPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
+ rightPanelCollapsed() {
+ this.editor.updateDimensions();
+ },
},
computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
+ ...mapState([
+ 'leftPanelCollapsed',
+ 'rightPanelCollapsed',
+ ]),
shouldHideEditor() {
return this.activeFile.binary && !this.activeFile.raw;
},
@@ -76,13 +113,14 @@ export default {
class="blob-viewer-container blob-editor-container"
>
<div
- v-show="shouldHideEditor"
+ v-if="shouldHideEditor"
v-html="activeFile.html"
>
</div>
<div
v-show="!shouldHideEditor"
ref="editor"
+ class="multi-file-editor-holder"
>
</div>
</div>
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index 75787ad6103..09ca11531b1 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -1,7 +1,8 @@
<script>
- import { mapActions, mapGetters } from 'vuex';
+ import { mapState } from 'vuex';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
+ import newDropdown from './new_dropdown/index.vue';
export default {
mixins: [
@@ -9,20 +10,22 @@
],
components: {
skeletonLoadingContainer,
+ newDropdown,
},
props: {
file: {
type: Object,
required: true,
},
+ showExtraColumns: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
- ...mapGetters([
- 'isCollapsed',
+ ...mapState([
+ 'leftPanelCollapsed',
]),
- isSubmodule() {
- return this.file.type === 'submodule';
- },
fileIcon() {
return {
'fa-spinner fa-spin': this.file.loading,
@@ -30,6 +33,12 @@
'fa-folder-open': !this.file.loading && this.file.opened,
};
},
+ isSubmodule() {
+ return this.file.type === 'submodule';
+ },
+ isTree() {
+ return this.file.type === 'tree';
+ },
levelIndentation() {
return {
marginLeft: `${this.file.level * 16}px`,
@@ -39,13 +48,39 @@
return this.file.id.substr(0, 8);
},
submoduleColSpan() {
- return !this.isCollapsed && this.isSubmodule ? 3 : 1;
+ return !this.leftPanelCollapsed && this.isSubmodule ? 3 : 1;
+ },
+ fileClass() {
+ if (this.file.type === 'blob') {
+ if (this.file.active) {
+ return 'file-open file-active';
+ }
+ return this.file.opened ? 'file-open' : '';
+ }
+ return '';
+ },
+ changedClass() {
+ return {
+ 'fa-circle unsaved-icon': this.file.changed || this.file.tempFile,
+ };
},
},
methods: {
- ...mapActions([
- 'clickedTreeRow',
- ]),
+ clickFile(row) {
+ // Manual Action if a tree is selected/opened
+ if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
+ this.$store.dispatch('toggleTreeOpen', {
+ endpoint: this.file.url,
+ tree: this.file,
+ });
+ }
+ this.$router.push(`/project${row.url}`);
+ },
+ },
+ updated() {
+ if (this.file.type === 'blob' && this.file.active) {
+ this.$el.scrollIntoView();
+ }
},
};
</script>
@@ -53,7 +88,8 @@
<template>
<tr
class="file"
- @click.prevent="clickedTreeRow(file)">
+ :class="fileClass"
+ @click="clickFile(file)">
<td
class="multi-file-table-name"
:colspan="submoduleColSpan"
@@ -66,11 +102,23 @@
>
</i>
<a
- :href="file.url"
class="repo-file-name"
>
{{ file.name }}
</a>
+ <new-dropdown
+ v-if="isTree"
+ :project-id="file.projectId"
+ :branch="file.branchId"
+ :path="file.path"
+ :parent="file"/>
+ <i
+ class="fa"
+ v-if="changedClass"
+ :class="changedClass"
+ aria-hidden="true"
+ >
+ </i>
<template v-if="isSubmodule && file.id">
@
<span class="commit-sha">
@@ -84,7 +132,7 @@
</template>
</td>
- <template v-if="!isCollapsed && !isSubmodule">
+ <template v-if="showExtraColumns && !isSubmodule">
<td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
<a
v-if="file.lastCommit.message"
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
index 34f0d51819a..34f0d51819a 100644
--- a/app/assets/javascripts/repo/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/ide/components/repo_file_buttons.vue
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue
index 8fa637d771f..7eb840c7608 100644
--- a/app/assets/javascripts/repo/components/repo_loading_file.vue
+++ b/app/assets/javascripts/ide/components/repo_loading_file.vue
@@ -1,5 +1,5 @@
<script>
- import { mapGetters } from 'vuex';
+ import { mapState } from 'vuex';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default {
@@ -7,8 +7,8 @@
skeletonLoadingContainer,
},
computed: {
- ...mapGetters([
- 'isCollapsed',
+ ...mapState([
+ 'leftPanelCollapsed',
]),
},
};
@@ -24,7 +24,7 @@
:small="true"
/>
</td>
- <template v-if="!isCollapsed">
+ <template v-if="!leftPanelCollapsed">
<td
class="hidden-sm hidden-xs">
<skeleton-loading-container
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue
index a2b305bbd05..7cd359ea4ed 100644
--- a/app/assets/javascripts/repo/components/repo_prev_directory.vue
+++ b/app/assets/javascripts/ide/components/repo_prev_directory.vue
@@ -1,16 +1,14 @@
<script>
- import { mapGetters, mapState, mapActions } from 'vuex';
+ import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState([
'parentTreeUrl',
- ]),
- ...mapGetters([
- 'isCollapsed',
+ 'leftPanelCollapsed',
]),
colSpanCondition() {
- return this.isCollapsed ? undefined : 3;
+ return this.leftPanelCollapsed ? undefined : 3;
},
},
methods: {
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
index 3d1e0297bd5..3d1e0297bd5 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/ide/components/repo_preview.vue
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index fb29a60df66..5bd63ac9ec5 100644
--- a/app/assets/javascripts/repo/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -27,16 +27,18 @@ export default {
methods: {
...mapActions([
- 'setFileActive',
'closeFile',
]),
+ clickFile(tab) {
+ this.$router.push(`/project${tab.url}`);
+ },
},
};
</script>
<template>
<li
- @click="setFileActive(tab)"
+ @click="clickFile(tab)"
>
<button
type="button"
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue
index ab0bef4f0ac..ab0bef4f0ac 100644
--- a/app/assets/javascripts/repo/components/repo_tabs.vue
+++ b/app/assets/javascripts/ide/components/repo_tabs.vue
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
new file mode 100644
index 00000000000..a9cbf8e370f
--- /dev/null
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -0,0 +1,101 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import store from './stores';
+import flash from '../flash';
+import {
+ getTreeEntry,
+} from './stores/utils';
+
+Vue.use(VueRouter);
+
+/**
+ * Routes below /-/ide/:
+
+/project/h5bp/html5-boilerplate/blob/master
+/project/h5bp/html5-boilerplate/blob/master/app/js/test.js
+
+/project/h5bp/html5-boilerplate/mr/123
+/project/h5bp/html5-boilerplate/mr/123/app/js/test.js
+
+/workspace/123
+/workspace/project/h5bp/html5-boilerplate/blob/my-special-branch
+/workspace/project/h5bp/html5-boilerplate/mr/123
+
+/ = /workspace
+
+/settings
+*/
+
+// Unfortunately Vue Router doesn't work without at least a fake component
+// If you do only data handling
+const EmptyRouterComponent = {
+ render(createElement) {
+ return createElement('div');
+ },
+};
+
+const router = new VueRouter({
+ mode: 'history',
+ base: `${gon.relative_url_root}/-/ide/`,
+ routes: [
+ {
+ path: '/project/:namespace/:project',
+ component: EmptyRouterComponent,
+ children: [
+ {
+ path: ':targetmode/:branch/*',
+ component: EmptyRouterComponent,
+ },
+ {
+ path: 'mr/:mrid',
+ component: EmptyRouterComponent,
+ },
+ ],
+ },
+ ],
+});
+
+router.beforeEach((to, from, next) => {
+ if (to.params.namespace && to.params.project) {
+ store.dispatch('getProjectData', {
+ namespace: to.params.namespace,
+ projectId: to.params.project,
+ })
+ .then(() => {
+ const fullProjectId = `${to.params.namespace}/${to.params.project}`;
+
+ if (to.params.branch) {
+ store.dispatch('getBranchData', {
+ projectId: fullProjectId,
+ branchId: to.params.branch,
+ });
+
+ store.dispatch('getTreeData', {
+ projectId: fullProjectId,
+ branch: to.params.branch,
+ endpoint: `/tree/${to.params.branch}`,
+ })
+ .then(() => {
+ if (to.params[0]) {
+ const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]);
+ if (treeEntry) {
+ store.dispatch('handleTreeEntryAction', treeEntry);
+ }
+ }
+ })
+ .catch((e) => {
+ flash('Error while loading the branch files. Please try again.');
+ throw e;
+ });
+ }
+ })
+ .catch((e) => {
+ flash('Error while loading the project data. Please try again.');
+ throw e;
+ });
+ }
+
+ next();
+});
+
+export default router;
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
new file mode 100644
index 00000000000..a96bd339f51
--- /dev/null
+++ b/app/assets/javascripts/ide/index.js
@@ -0,0 +1,55 @@
+import Vue from 'vue';
+import { mapActions } from 'vuex';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import ide from './components/ide.vue';
+
+import store from './stores';
+import router from './ide_router';
+import Translate from '../vue_shared/translate';
+import ContextualSidebar from '../contextual_sidebar';
+
+function initIde(el) {
+ if (!el) return null;
+
+ return new Vue({
+ el,
+ store,
+ router,
+ components: {
+ ide,
+ },
+ methods: {
+ ...mapActions([
+ 'setInitialData',
+ ]),
+ },
+ created() {
+ const data = el.dataset;
+
+ this.setInitialData({
+ endpoints: {
+ rootEndpoint: data.url,
+ newMergeRequestUrl: data.newMergeRequestUrl,
+ rootUrl: data.rootUrl,
+ },
+ canCommit: convertPermissionToBoolean(data.canCommit),
+ onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch),
+ path: data.currentPath,
+ isRoot: convertPermissionToBoolean(data.root),
+ isInitialRoot: convertPermissionToBoolean(data.root),
+ });
+ },
+ render(createElement) {
+ return createElement('ide');
+ },
+ });
+}
+
+const ideElement = document.getElementById('ide');
+
+Vue.use(Translate);
+
+initIde(ideElement);
+
+const contextualSidebar = new ContextualSidebar();
+contextualSidebar.bindEvents();
diff --git a/app/assets/javascripts/repo/lib/common/disposable.js b/app/assets/javascripts/ide/lib/common/disposable.js
index 84b29bdb600..84b29bdb600 100644
--- a/app/assets/javascripts/repo/lib/common/disposable.js
+++ b/app/assets/javascripts/ide/lib/common/disposable.js
diff --git a/app/assets/javascripts/repo/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 23c4811e6c0..14d9fe4771e 100644
--- a/app/assets/javascripts/repo/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -28,6 +28,14 @@ export default class Model {
return this.model.uri.toString();
}
+ get language() {
+ return this.model.getModeId();
+ }
+
+ get eol() {
+ return this.model.getEOL() === '\n' ? 'LF' : 'CRLF';
+ }
+
get path() {
return this.file.path;
}
diff --git a/app/assets/javascripts/repo/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js
index fd462252795..fd462252795 100644
--- a/app/assets/javascripts/repo/lib/common/model_manager.js
+++ b/app/assets/javascripts/ide/lib/common/model_manager.js
diff --git a/app/assets/javascripts/repo/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js
index 0954b7973c4..0954b7973c4 100644
--- a/app/assets/javascripts/repo/lib/decorations/controller.js
+++ b/app/assets/javascripts/ide/lib/decorations/controller.js
diff --git a/app/assets/javascripts/repo/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js
index dc0b1c95e59..dc0b1c95e59 100644
--- a/app/assets/javascripts/repo/lib/diff/controller.js
+++ b/app/assets/javascripts/ide/lib/diff/controller.js
diff --git a/app/assets/javascripts/repo/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js
index 0e37f5c4704..0e37f5c4704 100644
--- a/app/assets/javascripts/repo/lib/diff/diff.js
+++ b/app/assets/javascripts/ide/lib/diff/diff.js
diff --git a/app/assets/javascripts/repo/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js
index e74c4046330..e74c4046330 100644
--- a/app/assets/javascripts/repo/lib/diff/diff_worker.js
+++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js
diff --git a/app/assets/javascripts/repo/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index db499444402..51e202b9348 100644
--- a/app/assets/javascripts/repo/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -22,6 +22,11 @@ export default class Editor {
this.modelManager = new ModelManager(this.monaco),
this.decorationsController = new DecorationsController(this),
);
+
+ this.debouncedUpdate = _.debounce(() => {
+ this.updateDimensions();
+ }, 200);
+ window.addEventListener('resize', this.debouncedUpdate, false);
}
createInstance(domElement) {
@@ -32,6 +37,9 @@ export default class Editor {
readOnly: false,
contextmenu: true,
scrollBeyondLastLine: false,
+ minimap: {
+ enabled: false,
+ },
}),
this.dirtyDiffController = new DirtyDiffController(
this.modelManager, this.decorationsController,
@@ -70,10 +78,32 @@ export default class Editor {
dispose() {
this.disposable.dispose();
+ window.removeEventListener('resize', this.debouncedUpdate);
// dispose main monaco instance
if (this.instance) {
this.instance = null;
}
}
+
+ updateDimensions() {
+ this.instance.layout();
+ }
+
+ setPosition({ lineNumber, column }) {
+ this.instance.revealPositionInCenter({
+ lineNumber,
+ column,
+ });
+ this.instance.setPosition({
+ lineNumber,
+ column,
+ });
+ }
+
+ onPositionChange(cb) {
+ this.disposable.add(
+ this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
+ );
+ }
}
diff --git a/app/assets/javascripts/repo/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index 701affc466e..701affc466e 100644
--- a/app/assets/javascripts/repo/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
diff --git a/app/assets/javascripts/repo/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js
index af83a1ec0b4..af83a1ec0b4 100644
--- a/app/assets/javascripts/repo/monaco_loader.js
+++ b/app/assets/javascripts/ide/monaco_loader.js
diff --git a/app/assets/javascripts/repo/services/index.js b/app/assets/javascripts/ide/services/index.js
index 994d325e991..1fb24e93f2e 100644
--- a/app/assets/javascripts/repo/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -23,8 +23,11 @@ export default {
return Vue.http.get(file.rawPath, { params: { format: 'json' } })
.then(res => res.text());
},
- getBranchData(projectId, currentBranch) {
- return Api.branchSingle(projectId, currentBranch);
+ getProjectData(namespace, project) {
+ return Api.project(`${namespace}/${project}`);
+ },
+ getBranchData(projectId, currentBranchId) {
+ return Api.branchSingle(projectId, currentBranchId);
},
createBranch(projectId, payload) {
const url = Api.buildUrl(Api.createBranchPath).replace(':id', projectId);
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
new file mode 100644
index 00000000000..c01046c8c76
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -0,0 +1,179 @@
+import Vue from 'vue';
+import { visitUrl } from '../../lib/utils/url_utility';
+import flash from '../../flash';
+import service from '../services';
+import * as types from './mutation_types';
+
+export const redirectToUrl = (_, url) => visitUrl(url);
+
+export const setInitialData = ({ commit }, data) =>
+ commit(types.SET_INITIAL_DATA, data);
+
+export const closeDiscardPopup = ({ commit }) =>
+ commit(types.TOGGLE_DISCARD_POPUP, false);
+
+export const discardAllChanges = ({ commit, getters, dispatch }) => {
+ const changedFiles = getters.changedFiles;
+
+ changedFiles.forEach((file) => {
+ commit(types.DISCARD_FILE_CHANGES, file);
+
+ if (file.tempFile) {
+ dispatch('closeFile', { file, force: true });
+ }
+ });
+};
+
+export const closeAllFiles = ({ state, dispatch }) => {
+ state.openFiles.forEach(file => dispatch('closeFile', { file }));
+};
+
+export const toggleEditMode = (
+ { state, commit, getters, dispatch },
+ force = false,
+) => {
+ const changedFiles = getters.changedFiles;
+
+ if (changedFiles.length && !force) {
+ commit(types.TOGGLE_DISCARD_POPUP, true);
+ } else {
+ commit(types.TOGGLE_EDIT_MODE);
+ commit(types.TOGGLE_DISCARD_POPUP, false);
+ dispatch('toggleBlobView');
+
+ if (!state.editMode) {
+ dispatch('discardAllChanges');
+ }
+ }
+};
+
+export const toggleBlobView = ({ commit, state }) => {
+ if (state.editMode) {
+ commit(types.SET_EDIT_MODE);
+ } else {
+ commit(types.SET_PREVIEW_MODE);
+ }
+};
+
+export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
+ if (side === 'left') {
+ commit(types.SET_LEFT_PANEL_COLLAPSED, collapsed);
+ } else {
+ commit(types.SET_RIGHT_PANEL_COLLAPSED, collapsed);
+ }
+};
+
+export const checkCommitStatus = ({ state }) =>
+ service
+ .getBranchData(state.currentProjectId, state.currentBranchId)
+ .then((data) => {
+ const { id } = data.commit;
+ const selectedBranch =
+ state.projects[state.currentProjectId].branches[state.currentBranchId];
+
+ if (selectedBranch.workingReference !== id) {
+ return true;
+ }
+
+ return false;
+ })
+ .catch(() => flash('Error checking branch data. Please try again.'));
+
+export const commitChanges = (
+ { commit, state, dispatch, getters },
+ { payload, newMr },
+) =>
+ service
+ .commit(state.currentProjectId, payload)
+ .then((data) => {
+ const { branch } = payload;
+ if (!data.short_id) {
+ flash(data.message);
+ return;
+ }
+
+ const selectedProject = state.projects[state.currentProjectId];
+ const lastCommit = {
+ commit_path: `${selectedProject.web_url}/commit/${data.id}`,
+ commit: {
+ message: data.message,
+ authored_date: data.committed_date,
+ },
+ };
+
+ flash(
+ `Your changes have been committed. Commit ${data.short_id} with ${
+ data.stats.additions
+ } additions, ${data.stats.deletions} deletions.`,
+ 'notice',
+ );
+
+ if (newMr) {
+ dispatch(
+ 'redirectToUrl',
+ `${
+ selectedProject.web_url
+ }/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
+ );
+ } else {
+ commit(types.SET_BRANCH_WORKING_REFERENCE, {
+ projectId: state.currentProjectId,
+ branchId: state.currentBranchId,
+ reference: data.id,
+ });
+
+ getters.changedFiles.forEach((entry) => {
+ commit(types.SET_LAST_COMMIT_DATA, {
+ entry,
+ lastCommit,
+ });
+ });
+
+ dispatch('discardAllChanges');
+ dispatch('closeAllFiles');
+
+ window.scrollTo(0, 0);
+ }
+ })
+ .catch(() => flash('Error committing changes. Please try again.'));
+
+export const createTempEntry = (
+ { state, dispatch },
+ { projectId, branchId, parent, name, type, content = '', base64 = false },
+) => {
+ const selectedParent = parent || state.trees[`${projectId}/${branchId}`];
+ if (type === 'tree') {
+ dispatch('createTempTree', {
+ projectId,
+ branchId,
+ parent: selectedParent,
+ name,
+ });
+ } else if (type === 'blob') {
+ dispatch('createTempFile', {
+ projectId,
+ branchId,
+ parent: selectedParent,
+ name,
+ base64,
+ content,
+ });
+ }
+};
+
+export const scrollToTab = () => {
+ Vue.nextTick(() => {
+ const tabs = document.getElementById('tabs');
+
+ if (tabs) {
+ const tabEl = tabs.querySelector('.active .repo-tab');
+
+ tabEl.focus();
+ }
+ });
+};
+
+export * from './actions/tree';
+export * from './actions/file';
+export * from './actions/project';
+export * from './actions/branch';
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
new file mode 100644
index 00000000000..32bdf7fec22
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/branch.js
@@ -0,0 +1,43 @@
+import service from '../../services';
+import flash from '../../../flash';
+import * as types from '../mutation_types';
+
+export const getBranchData = (
+ { commit, state, dispatch },
+ { projectId, branchId, force = false } = {},
+) => new Promise((resolve, reject) => {
+ if ((typeof state.projects[`${projectId}`] === 'undefined' ||
+ !state.projects[`${projectId}`].branches[branchId])
+ || force) {
+ service.getBranchData(`${projectId}`, branchId)
+ .then((data) => {
+ const { id } = data.commit;
+ commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
+ commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+ resolve(data);
+ })
+ .catch(() => {
+ flash('Error loading branch data. Please try again.');
+ reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
+ });
+ } else {
+ resolve(state.projects[`${projectId}`].branches[branchId]);
+ }
+});
+
+export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
+ state.currentProjectId,
+ {
+ branch,
+ ref: state.currentBranchId,
+ },
+)
+.then(res => res.json())
+.then((data) => {
+ const branchName = data.name;
+ const url = location.href.replace(state.currentBranchId, branchName);
+
+ if (this.$router) this.$router.push(url);
+
+ commit(types.SET_CURRENT_BRANCH, branchName);
+});
diff --git a/app/assets/javascripts/repo/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 5bae4fa826a..0f27d5bf1c3 100644
--- a/app/assets/javascripts/repo/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -2,9 +2,9 @@ import { normalizeHeaders } from '../../../lib/utils/common_utils';
import flash from '../../../flash';
import service from '../../services';
import * as types from '../mutation_types';
+import router from '../../ide_router';
import {
findEntry,
- pushState,
setPageTitle,
createTemp,
findIndexOfFile,
@@ -25,7 +25,7 @@ export const closeFile = ({ commit, state, dispatch }, { file, force = false })
dispatch('setFileActive', nextFileToOpen);
} else if (!state.openFiles.length) {
- pushState(file.parentTreeUrl);
+ router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
}
dispatch('getLastCommitData');
@@ -45,6 +45,9 @@ export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
// reset hash for line highlighting
location.hash = '';
+
+ commit(types.SET_CURRENT_PROJECT, file.projectId);
+ commit(types.SET_CURRENT_BRANCH, file.branchId);
};
export const getFileData = ({ state, commit, dispatch }, file) => {
@@ -63,8 +66,6 @@ export const getFileData = ({ state, commit, dispatch }, file) => {
commit(types.TOGGLE_FILE_OPEN, file);
dispatch('setFileActive', file);
commit(types.TOGGLE_LOADING, file);
-
- pushState(file.url);
})
.catch(() => {
commit(types.TOGGLE_LOADING, file);
@@ -82,21 +83,39 @@ export const changeFileContent = ({ commit }, { file, content }) => {
commit(types.UPDATE_FILE_CONTENT, { file, content });
};
-export const createTempFile = ({ state, commit, dispatch }, { tree, name, content = '', base64 = '' }) => {
+export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
+ commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
+};
+
+export const setFileEOL = ({ state, commit }, { eol }) => {
+ commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
+};
+
+export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
+ commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
+};
+
+export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
+ const path = parent.path !== undefined ? parent.path : '';
+ // We need to do the replacement otherwise the web_url + file.url duplicate
+ const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
const file = createTemp({
- name: name.replace(`${state.path}/`, ''),
- path: tree.path,
+ projectId,
+ branchId,
+ name: name.replace(`${path}/`, ''),
+ path,
type: 'blob',
- level: tree.level !== undefined ? tree.level + 1 : 0,
+ level: parent.level !== undefined ? parent.level + 1 : 0,
changed: true,
content,
base64,
+ url: newUrl,
});
- if (findEntry(tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`);
+ if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`);
commit(types.CREATE_TMP_FILE, {
- parent: tree,
+ parent,
file,
});
commit(types.TOGGLE_FILE_OPEN, file);
@@ -106,5 +125,7 @@ export const createTempFile = ({ state, commit, dispatch }, { tree, name, conten
dispatch('toggleEditMode', true);
}
+ router.push(`/project${file.url}`);
+
return Promise.resolve(file);
};
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
new file mode 100644
index 00000000000..75e332090cb
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -0,0 +1,25 @@
+import service from '../../services';
+import flash from '../../../flash';
+import * as types from '../mutation_types';
+
+// eslint-disable-next-line import/prefer-default-export
+export const getProjectData = (
+ { commit, state, dispatch },
+ { namespace, projectId, force = false } = {},
+) => new Promise((resolve, reject) => {
+ if (!state.projects[`${namespace}/${projectId}`] || force) {
+ service.getProjectData(namespace, projectId)
+ .then(res => res.data)
+ .then((data) => {
+ commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
+ if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
+ resolve(data);
+ })
+ .catch(() => {
+ flash('Error loading project data. Please try again.');
+ reject(new Error(`Project not loaded ${namespace}/${projectId}`));
+ });
+ } else {
+ resolve(state.projects[`${namespace}/${projectId}`]);
+ }
+});
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
new file mode 100644
index 00000000000..25909400a75
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -0,0 +1,188 @@
+import { visitUrl } from '../../../lib/utils/url_utility';
+import { normalizeHeaders } from '../../../lib/utils/common_utils';
+import flash from '../../../flash';
+import service from '../../services';
+import * as types from '../mutation_types';
+import router from '../../ide_router';
+import {
+ setPageTitle,
+ findEntry,
+ createTemp,
+ createOrMergeEntry,
+} from '../utils';
+
+export const getTreeData = (
+ { commit, state, dispatch },
+ { endpoint, tree = null, projectId, branch, force = false } = {},
+) => new Promise((resolve, reject) => {
+ // We already have the base tree so we resolve immediately
+ if (!tree && state.trees[`${projectId}/${branch}`] && !force) {
+ resolve();
+ } else {
+ if (tree) commit(types.TOGGLE_LOADING, tree);
+ const selectedProject = state.projects[projectId];
+ // We are merging the web_url that we got on the project info with the endpoint
+ // we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint
+ const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, '');
+ if (completeEndpoint && (!tree || !tree.tempFile)) {
+ service.getTreeData(completeEndpoint)
+ .then((res) => {
+ const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
+
+ setPageTitle(pageTitle);
+
+ return res.json();
+ })
+ .then((data) => {
+ if (!state.isInitialRoot) {
+ commit(types.SET_ROOT, data.path === '/');
+ }
+
+ dispatch('updateDirectoryData', { data, tree, projectId, branch });
+ const selectedTree = tree || state.trees[`${projectId}/${branch}`];
+
+ commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
+ commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path });
+ if (tree) commit(types.TOGGLE_LOADING, selectedTree);
+
+ const prevLastCommitPath = selectedTree.lastCommitPath;
+ if (prevLastCommitPath !== null) {
+ dispatch('getLastCommitData', selectedTree);
+ }
+ resolve(data);
+ })
+ .catch((e) => {
+ flash('Error loading tree data. Please try again.');
+ if (tree) commit(types.TOGGLE_LOADING, tree);
+ reject(e);
+ });
+ } else {
+ resolve();
+ }
+ }
+});
+
+export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
+ if (tree.opened) {
+ // send empty data to clear the tree
+ const data = { trees: [], blobs: [], submodules: [] };
+
+ dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId });
+ } else {
+ dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId });
+ }
+
+ commit(types.TOGGLE_TREE_OPEN, tree);
+};
+
+export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
+ if (row.type === 'tree') {
+ dispatch('toggleTreeOpen', {
+ endpoint: row.url,
+ tree: row,
+ });
+ } else if (row.type === 'submodule') {
+ commit(types.TOGGLE_LOADING, row);
+ visitUrl(row.url);
+ } else if (row.type === 'blob' && row.opened) {
+ dispatch('setFileActive', row);
+ } else {
+ dispatch('getFileData', row);
+ }
+};
+
+export const createTempTree = (
+ { state, commit, dispatch },
+ { projectId, branchId, parent, name },
+) => {
+ let selectedTree = parent;
+ const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
+
+ dirNames.forEach((dirName) => {
+ const foundEntry = findEntry(selectedTree.tree, 'tree', dirName);
+
+ if (!foundEntry) {
+ const path = selectedTree.path !== undefined ? selectedTree.path : '';
+ const tmpEntry = createTemp({
+ projectId,
+ branchId,
+ name: dirName,
+ path,
+ type: 'tree',
+ level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
+ tree: [],
+ url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
+ });
+
+ commit(types.CREATE_TMP_TREE, {
+ parent: selectedTree,
+ tmpEntry,
+ });
+ commit(types.TOGGLE_TREE_OPEN, tmpEntry);
+
+ router.push(`/project${tmpEntry.url}`);
+
+ selectedTree = tmpEntry;
+ } else {
+ selectedTree = foundEntry;
+ }
+ });
+};
+
+export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
+ if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
+
+ service.getTreeLastCommit(tree.lastCommitPath)
+ .then((res) => {
+ const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
+
+ commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
+
+ return res.json();
+ })
+ .then((data) => {
+ data.forEach((lastCommit) => {
+ const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
+
+ if (entry) {
+ commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
+ }
+ });
+
+ dispatch('getLastCommitData', tree);
+ })
+ .catch(() => flash('Error fetching log data.'));
+};
+
+export const updateDirectoryData = (
+ { commit, state },
+ { data, tree, projectId, branch },
+) => {
+ if (!tree) {
+ const existingTree = state.trees[`${projectId}/${branch}`];
+ if (!existingTree) {
+ commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` });
+ }
+ }
+
+ const selectedTree = tree || state.trees[`${projectId}/${branch}`];
+ const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0;
+ const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
+ const createEntry = (entry, type) => createOrMergeEntry({
+ tree: selectedTree,
+ projectId: `${projectId}`,
+ branchId: branch,
+ entry,
+ level,
+ type,
+ parentTreeUrl,
+ });
+
+ const formattedData = [
+ ...data.trees.map(t => createEntry(t, 'tree')),
+ ...data.submodules.map(m => createEntry(m, 'submodule')),
+ ...data.blobs.map(b => createEntry(b, 'blob')),
+ ];
+
+ commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData });
+};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
new file mode 100644
index 00000000000..6b51ccff817
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -0,0 +1,19 @@
+export const changedFiles = state => state.openFiles.filter(file => file.changed);
+
+export const activeFile = state => state.openFiles.find(file => file.active) || null;
+
+export const activeFileExtension = (state) => {
+ const file = activeFile(state);
+ return file ? `.${file.path.split('.').pop()}` : '';
+};
+
+export const canEditFile = (state) => {
+ const currentActiveFile = activeFile(state);
+
+ return state.canCommit &&
+ (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
+};
+
+export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
+
+export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
diff --git a/app/assets/javascripts/repo/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index 6ac9bfd8189..6ac9bfd8189 100644
--- a/app/assets/javascripts/repo/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
diff --git a/app/assets/javascripts/repo/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index bc3390f1506..4e3c10972ba 100644
--- a/app/assets/javascripts/repo/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -1,16 +1,27 @@
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_COMMIT_REF = 'SET_COMMIT_REF';
export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
export const SET_ROOT = 'SET_ROOT';
-export const SET_PREVIOUS_URL = 'SET_PREVIOUS_URL';
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
+export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
+export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
+
+// Project Mutation Types
+export const SET_PROJECT = 'SET_PROJECT';
+export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
+export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
+
+// Branch Mutation Types
+export const SET_BRANCH = 'SET_BRANCH';
+export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
+export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
// Tree mutation types
export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
export const CREATE_TMP_TREE = 'CREATE_TMP_TREE';
export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
+export const CREATE_TREE = 'CREATE_TREE';
// File mutation types
export const SET_FILE_DATA = 'SET_FILE_DATA';
@@ -18,6 +29,9 @@ export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
+export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
+export const SET_FILE_POSITION = 'SET_FILE_POSITION';
+export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
@@ -28,3 +42,4 @@ export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP';
export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
+
diff --git a/app/assets/javascripts/repo/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index ae2ba5bedf7..2fed9019cb6 100644
--- a/app/assets/javascripts/repo/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,4 +1,5 @@
import * as types from './mutation_types';
+import projectMutations from './mutations/project';
import fileMutations from './mutations/file';
import treeMutations from './mutations/tree';
import branchMutations from './mutations/branch';
@@ -32,29 +33,32 @@ export default {
discardPopupOpen,
});
},
- [types.SET_COMMIT_REF](state, ref) {
- Object.assign(state, {
- currentRef: ref,
- });
- },
[types.SET_ROOT](state, isRoot) {
Object.assign(state, {
isRoot,
isInitialRoot: isRoot,
});
},
- [types.SET_PREVIOUS_URL](state, previousUrl) {
+ [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
+ Object.assign(state, {
+ leftPanelCollapsed: collapsed,
+ });
+ },
+ [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
Object.assign(state, {
- previousUrl,
+ rightPanelCollapsed: collapsed,
});
},
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
Object.assign(entry.lastCommit, {
+ id: lastCommit.commit.id,
url: lastCommit.commit_path,
message: lastCommit.commit.message,
+ author: lastCommit.commit.author_name,
updatedAt: lastCommit.commit.authored_date,
});
},
+ ...projectMutations,
...fileMutations,
...treeMutations,
...branchMutations,
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
new file mode 100644
index 00000000000..04b9582c5bb
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/mutations/branch.js
@@ -0,0 +1,28 @@
+import * as types from '../mutation_types';
+
+export default {
+ [types.SET_CURRENT_BRANCH](state, currentBranchId) {
+ Object.assign(state, {
+ currentBranchId,
+ });
+ },
+ [types.SET_BRANCH](state, { projectPath, branchName, branch }) {
+ // Add client side properties
+ Object.assign(branch, {
+ treeId: `${projectPath}/${branchName}`,
+ active: true,
+ workingReference: '',
+ });
+
+ Object.assign(state.projects[projectPath], {
+ branches: {
+ [branchName]: branch,
+ },
+ });
+ },
+ [types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
+ Object.assign(state.projects[projectId].branches[branchId], {
+ workingReference: reference,
+ });
+ },
+};
diff --git a/app/assets/javascripts/repo/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index f9ba80b9dc2..5f3655b0092 100644
--- a/app/assets/javascripts/repo/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -6,6 +6,10 @@ export default {
Object.assign(file, {
active,
});
+
+ Object.assign(state, {
+ selectedFile: file,
+ });
},
[types.TOGGLE_FILE_OPEN](state, file) {
Object.assign(file, {
@@ -42,6 +46,22 @@ export default {
changed,
});
},
+ [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
+ Object.assign(file, {
+ fileLanguage,
+ });
+ },
+ [types.SET_FILE_EOL](state, { file, eol }) {
+ Object.assign(file, {
+ eol,
+ });
+ },
+ [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
+ Object.assign(file, {
+ editorRow,
+ editorColumn,
+ });
+ },
[types.DISCARD_FILE_CHANGES](state, file) {
Object.assign(file, {
content: '',
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
new file mode 100644
index 00000000000..2816562a919
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/mutations/project.js
@@ -0,0 +1,23 @@
+import * as types from '../mutation_types';
+
+export default {
+ [types.SET_CURRENT_PROJECT](state, currentProjectId) {
+ Object.assign(state, {
+ currentProjectId,
+ });
+ },
+ [types.SET_PROJECT](state, { projectPath, project }) {
+ // Add client side properties
+ Object.assign(project, {
+ tree: [],
+ branches: {},
+ active: true,
+ });
+
+ Object.assign(state, {
+ projects: Object.assign({}, state.projects, {
+ [projectPath]: project,
+ }),
+ });
+ },
+};
diff --git a/app/assets/javascripts/repo/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js
index 130221c9fda..4fe438ab465 100644
--- a/app/assets/javascripts/repo/stores/mutations/tree.js
+++ b/app/assets/javascripts/ide/stores/mutations/tree.js
@@ -6,6 +6,15 @@ export default {
opened: !tree.opened,
});
},
+ [types.CREATE_TREE](state, { treePath }) {
+ Object.assign(state, {
+ trees: Object.assign({}, state.trees, {
+ [treePath]: {
+ tree: [],
+ },
+ }),
+ });
+ },
[types.SET_DIRECTORY_DATA](state, { data, tree }) {
Object.assign(tree, {
tree: data,
diff --git a/app/assets/javascripts/repo/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index 0068834831e..539e382830f 100644
--- a/app/assets/javascripts/repo/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -1,10 +1,10 @@
export default () => ({
canCommit: false,
- currentBranch: '',
- currentBlobView: 'repo-preview',
- currentRef: '',
+ currentProjectId: '',
+ currentBranchId: '',
+ currentBlobView: 'repo-editor',
discardPopupOpen: false,
- editMode: false,
+ editMode: true,
endpoints: {},
isRoot: false,
isInitialRoot: false,
@@ -12,13 +12,11 @@ export default () => ({
loading: false,
onTopOfBranch: false,
openFiles: [],
+ selectedFile: null,
path: '',
- project: {
- id: 0,
- name: '',
- url: '',
- },
parentTreeUrl: '',
- previousUrl: '',
- tree: [],
+ trees: {},
+ projects: {},
+ leftPanelCollapsed: false,
+ rightPanelCollapsed: true,
});
diff --git a/app/assets/javascripts/repo/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index fae1f4439a9..29e3ab5d040 100644
--- a/app/assets/javascripts/repo/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -2,6 +2,8 @@ export const dataStructure = () => ({
id: '',
key: '',
type: '',
+ projectId: '',
+ branchId: '',
name: '',
url: '',
path: '',
@@ -15,9 +17,11 @@ export const dataStructure = () => ({
changed: false,
lastCommitPath: '',
lastCommit: {
+ id: '',
url: '',
message: '',
updatedAt: '',
+ author: '',
},
tree_url: '',
blamePath: '',
@@ -31,11 +35,17 @@ export const dataStructure = () => ({
parentTreeUrl: '',
renderError: false,
base64: false,
+ editorRow: 1,
+ editorColumn: 1,
+ fileLanguage: '',
+ eol: '',
});
export const decorateData = (entity) => {
const {
id,
+ projectId,
+ branchId,
type,
url,
name,
@@ -56,6 +66,8 @@ export const decorateData = (entity) => {
return {
...dataStructure(),
id,
+ projectId,
+ branchId,
key: `${name}-${type}-${id}`,
type,
name,
@@ -75,24 +87,51 @@ export const decorateData = (entity) => {
};
};
-export const findEntry = (state, type, name) => state.tree.find(
+/*
+ Takes the multi-dimensional tree and returns a flattened array.
+ This allows for the table to recursively render the table rows but keeps the data
+ structure nested to make it easier to add new files/directories.
+*/
+export const treeList = (state, treeId) => {
+ const baseTree = state.trees[treeId];
+ if (baseTree) {
+ const mapTree = arr => (!arr.tree || !arr.tree.length ?
+ [] : _.map(arr.tree, a => [a, mapTree(a)]));
+
+ return _.chain(baseTree.tree)
+ .map(arr => [arr, mapTree(arr)])
+ .flatten()
+ .value();
+ }
+ return [];
+};
+
+export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
+
+export const getTreeEntry = (store, treeId, path) => {
+ const fileList = treeList(store.state, treeId);
+ return fileList ? fileList.find(file => file.path === path) : null;
+};
+
+export const findEntry = (tree, type, name) => tree.find(
f => f.type === type && f.name === name,
);
+
export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
export const setPageTitle = (title) => {
document.title = title;
};
-export const pushState = (url) => {
- history.pushState({ url }, '', url);
-};
-
-export const createTemp = ({ name, path, type, level, changed, content, base64 }) => {
+export const createTemp = ({
+ projectId, branchId, name, path, type, level, changed, content, base64, url,
+}) => {
const treePath = path ? `${path}/${name}` : name;
return decorateData({
id: new Date().getTime().toString(),
+ projectId,
+ branchId,
name,
type,
tempFile: true,
@@ -104,11 +143,18 @@ export const createTemp = ({ name, path, type, level, changed, content, base64 }
level,
base64,
renderError: base64,
+ url,
});
};
-export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level }) => {
- const found = findEntry(tree, type, entry.name);
+export const createOrMergeEntry = ({ tree,
+ projectId,
+ branchId,
+ entry,
+ type,
+ parentTreeUrl,
+ level }) => {
+ const found = findEntry(tree.tree || tree, type, entry.name);
if (found) {
return Object.assign({}, found, {
@@ -120,6 +166,8 @@ export const createOrMergeEntry = ({ tree, entry, type, parentTreeUrl, level })
return decorateData({
...entry,
+ projectId,
+ branchId,
type,
parentTreeUrl,
level,
diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js
index 5d4c1851fe5..e61b37a2d1f 100644
--- a/app/assets/javascripts/init_issuable_sidebar.js
+++ b/app/assets/javascripts/init_issuable_sidebar.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
-/* global MilestoneSelect */
+
+import MilestoneSelect from './milestone_select';
import LabelsSelect from './labels_select';
import IssuableContext from './issuable_context';
import Sidebar from './right_sidebar';
diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js
index 2cbb70220d0..b6ff97d1279 100644
--- a/app/assets/javascripts/init_legacy_filters.js
+++ b/app/assets/javascripts/init_legacy_filters.js
@@ -1,9 +1,9 @@
/* eslint-disable no-new */
import LabelsSelect from './labels_select';
-/* global MilestoneSelect */
import subscriptionSelect from './subscription_select';
import UsersSelect from './users_select';
import issueStatusSelect from './issue_status_select';
+import MilestoneSelect from './milestone_select';
export default () => {
new UsersSelect();
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index bf77b93b643..2056efe701b 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -1,8 +1,7 @@
/* eslint-disable class-methods-use-this, no-new */
-/* global MilestoneSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
-import './milestone_select';
+import MilestoneSelect from './milestone_select';
import issueStatusSelect from './issue_status_select';
import subscriptionSelect from './subscription_select';
import LabelsSelect from './labels_select';
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index e1b2506e437..f94b0fdbb5e 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
-import { bytesToKiB } from './lib/utils/number_utils';
+import { numberToHumanSize } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
import { timeFor } from './lib/utils/datetime_utility';
@@ -197,7 +197,7 @@ export default class Job {
// we need to show a message warning the user about that.
if (this.logBytes < log.total) {
// size is in bytes, we need to calculate KiB
- const size = bytesToKiB(this.logBytes);
+ const size = numberToHumanSize(this.logBytes);
$('.js-truncated-info-size').html(`${size}`);
this.$truncatedInfo.removeClass('hidden');
} else {
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 2e5e818d61d..0e854295fe3 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,237 +1,228 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
import { timeFor } from './lib/utils/datetime_utility';
-(function() {
- this.MilestoneSelect = (function() {
- function MilestoneSelect(currentProject, els, options = {}) {
- var _this, $els;
- if (currentProject != null) {
- _this = this;
- this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
- }
+export default class MilestoneSelect {
+ constructor(currentProject, els, options = {}) {
+ if (currentProject !== null) {
+ this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
+ }
- $els = $(els);
+ this.init(els, options);
+ }
- if (!els) {
- $els = $('.js-milestone-select');
- }
+ init(els, options) {
+ let $els = $(els);
- $els.each(function(i, dropdown) {
- var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove;
- $dropdown = $(dropdown);
- projectId = $dropdown.data('project-id');
- milestonesUrl = $dropdown.data('milestones');
- issueUpdateURL = $dropdown.data('issueUpdate');
- showNo = $dropdown.data('show-no');
- showAny = $dropdown.data('show-any');
- showMenuAbove = $dropdown.data('showMenuAbove');
- showUpcoming = $dropdown.data('show-upcoming');
- showStarted = $dropdown.data('show-started');
- useId = $dropdown.data('use-id');
- defaultLabel = $dropdown.data('default-label');
- defaultNo = $dropdown.data('default-no');
- issuableId = $dropdown.data('issuable-id');
- abilityName = $dropdown.data('ability-name');
- $selectbox = $dropdown.closest('.selectbox');
- $block = $selectbox.closest('.block');
- $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
- $value = $block.find('.value');
- $loading = $block.find('.block-loading').fadeOut();
- selectedMilestoneDefault = (showAny ? '' : null);
- selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault);
- selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
- if (issueUpdateURL) {
- milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
- milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
- collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
- }
- return $dropdown.glDropdown({
- showMenuAbove: showMenuAbove,
- data: function(term, callback) {
- return $.ajax({
- url: milestonesUrl
- }).done(function(data) {
- var extraOptions = [];
- if (showAny) {
- extraOptions.push({
- id: 0,
- name: '',
- title: 'Any Milestone'
- });
- }
- if (showNo) {
- extraOptions.push({
- id: -1,
- name: 'No Milestone',
- title: 'No Milestone'
- });
- }
- if (showUpcoming) {
- extraOptions.push({
- id: -2,
- name: '#upcoming',
- title: 'Upcoming'
- });
- }
- if (showStarted) {
- extraOptions.push({
- id: -3,
- name: '#started',
- title: 'Started'
- });
- }
- if (extraOptions.length) {
- extraOptions.push('divider');
- }
+ if (!els) {
+ $els = $('.js-milestone-select');
+ }
- callback(extraOptions.concat(data));
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
- });
- },
- renderRow: function(milestone) {
- return `
- <li data-milestone-id="${milestone.name}">
- <a href='#' class='dropdown-menu-milestone-link'>
- ${_.escape(milestone.title)}
- </a>
- </li>
- `;
- },
- filterable: true,
- search: {
- fields: ['title']
- },
- selectable: true,
- toggleLabel: function(selected, el, e) {
- if (selected && 'id' in selected && $(el).hasClass('is-active')) {
- return selected.title;
- } else {
- return defaultLabel;
- }
- },
- defaultLabel: defaultLabel,
- fieldName: $dropdown.data('field-name'),
- text: function(milestone) {
- return _.escape(milestone.title);
- },
- id: function(milestone) {
- if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
- return milestone.name;
- } else {
- return milestone.id;
- }
- },
- isSelected: function(milestone) {
- return milestone.name === selectedMilestone;
- },
- hidden: function() {
- $selectbox.hide();
- // display:block overrides the hide-collapse rule
- return $value.css('display', '');
- },
- opened: function(e) {
- const $el = $(e.currentTarget);
- if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
- selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
- }
- $('a.is-active', $el).removeClass('is-active');
- $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
- },
- vue: $dropdown.hasClass('js-issue-board-sidebar'),
- clicked: function(clickEvent) {
- const { $el, e } = clickEvent;
- let selected = clickEvent.selectedObj;
+ $els.each((i, dropdown) => {
+ let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault;
+ const $dropdown = $(dropdown);
+ const projectId = $dropdown.data('project-id');
+ const milestonesUrl = $dropdown.data('milestones');
+ const issueUpdateURL = $dropdown.data('issueUpdate');
+ const showNo = $dropdown.data('show-no');
+ const showAny = $dropdown.data('show-any');
+ const showMenuAbove = $dropdown.data('showMenuAbove');
+ const showUpcoming = $dropdown.data('show-upcoming');
+ const showStarted = $dropdown.data('show-started');
+ const useId = $dropdown.data('use-id');
+ const defaultLabel = $dropdown.data('default-label');
+ const defaultNo = $dropdown.data('default-no');
+ const issuableId = $dropdown.data('issuable-id');
+ const abilityName = $dropdown.data('ability-name');
+ const $selectBox = $dropdown.closest('.selectbox');
+ const $block = $selectBox.closest('.block');
+ const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
+ const $value = $block.find('.value');
+ const $loading = $block.find('.block-loading').fadeOut();
+ selectedMilestoneDefault = (showAny ? '' : null);
+ selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault);
+ selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
- var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
- if (!selected) return;
+ if (issueUpdateURL) {
+ milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+ milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
+ collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
+ }
+ return $dropdown.glDropdown({
+ showMenuAbove: showMenuAbove,
+ data: (term, callback) => $.ajax({
+ url: milestonesUrl
+ }).done((data) => {
+ const extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: 0,
+ name: '',
+ title: 'Any Milestone'
+ });
+ }
+ if (showNo) {
+ extraOptions.push({
+ id: -1,
+ name: 'No Milestone',
+ title: 'No Milestone'
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: 'Upcoming'
+ });
+ }
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: 'Started'
+ });
+ }
+ if (extraOptions.length) {
+ extraOptions.push('divider');
+ }
- if (options.handleClick) {
- e.preventDefault();
- options.handleClick(selected);
- return;
- }
+ callback(extraOptions.concat(data));
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ }),
+ renderRow: milestone => `
+ <li data-milestone-id="${milestone.name}">
+ <a href='#' class='dropdown-menu-milestone-link'>
+ ${_.escape(milestone.title)}
+ </a>
+ </li>
+ `,
+ filterable: true,
+ search: {
+ fields: ['title']
+ },
+ selectable: true,
+ toggleLabel: (selected, el, e) => {
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
+ return selected.title;
+ } else {
+ return defaultLabel;
+ }
+ },
+ defaultLabel: defaultLabel,
+ fieldName: $dropdown.data('field-name'),
+ text: milestone => _.escape(milestone.title),
+ id: (milestone) => {
+ if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
+ return milestone.name;
+ } else {
+ return milestone.id;
+ }
+ },
+ isSelected: milestone => milestone.name === selectedMilestone,
+ hidden: () => {
+ $selectBox.hide();
+ // display:block overrides the hide-collapse rule
+ return $value.css('display', '');
+ },
+ opened: (e) => {
+ const $el = $(e.currentTarget);
+ if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
+ selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
+ }
+ $('a.is-active', $el).removeClass('is-active');
+ $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
+ },
+ vue: $dropdown.hasClass('js-issue-board-sidebar'),
+ clicked: (clickEvent) => {
+ const { $el, e } = clickEvent;
+ let selected = clickEvent.selectedObj;
- page = $('body').attr('data-page');
- isIssueIndex = page === 'projects:issues:index';
- isMRIndex = (page === page && page === 'projects:merge_requests:index');
- isSelecting = (selected.name !== selectedMilestone);
- selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
- if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
- e.preventDefault();
- return;
- }
+ let data, boardsStore;
+ if (!selected) return;
- if ($dropdown.closest('.add-issues-modal').length) {
- boardsStore = gl.issueBoards.ModalStore.store.filter;
- }
+ if (options.handleClick) {
+ e.preventDefault();
+ options.handleClick(selected);
+ return;
+ }
- if (boardsStore) {
- boardsStore[$dropdown.data('field-name')] = selected.name;
- e.preventDefault();
- } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
- return Issuable.filterResults($dropdown.closest('form'));
- } else if ($dropdown.hasClass('js-filter-submit')) {
- return $dropdown.closest('form').submit();
- } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
- if (selected.id !== -1 && isSelecting) {
- gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
- id: selected.id,
- title: selected.name
- }));
- } else {
- gl.issueBoards.boardStoreIssueDelete('milestone');
- }
+ const page = $('body').attr('data-page');
+ const isIssueIndex = page === 'projects:issues:index';
+ const isMRIndex = (page === page && page === 'projects:merge_requests:index');
+ const isSelecting = (selected.name !== selectedMilestone);
+ selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
+ if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
+ e.preventDefault();
+ return;
+ }
- $dropdown.trigger('loading.gl.dropdown');
- $loading.removeClass('hidden').fadeIn();
+ if ($dropdown.closest('.add-issues-modal').length) {
+ boardsStore = gl.issueBoards.ModalStore.store.filter;
+ }
- gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
- .then(function () {
- $dropdown.trigger('loaded.gl.dropdown');
- $loading.fadeOut();
- })
- .catch(() => {
- $loading.fadeOut();
- });
+ if (boardsStore) {
+ boardsStore[$dropdown.data('field-name')] = selected.name;
+ e.preventDefault();
+ } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
+ if (selected.id !== -1 && isSelecting) {
+ gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
+ id: selected.id,
+ title: selected.name
+ }));
} else {
- selected = $selectbox.find('input[type="hidden"]').val();
- data = {};
- data[abilityName] = {};
- data[abilityName].milestone_id = selected != null ? selected : null;
- $loading.removeClass('hidden').fadeIn();
- $dropdown.trigger('loading.gl.dropdown');
- return $.ajax({
- type: 'PUT',
- url: issueUpdateURL,
- data: data
- }).done(function(data) {
+ gl.issueBoards.boardStoreIssueDelete('milestone');
+ }
+
+ $dropdown.trigger('loading.gl.dropdown');
+ $loading.removeClass('hidden').fadeIn();
+
+ gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
+ .then(() => {
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
- $selectbox.hide();
- $value.css('display', '');
- if (data.milestone != null) {
- data.milestone.full_path = _this.currentProject.full_path;
- data.milestone.remaining = timeFor(data.milestone.due_date);
- data.milestone.name = data.milestone.title;
- $value.html(milestoneLinkTemplate(data.milestone));
- return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
- } else {
- $value.html(milestoneLinkNoneTemplate);
- return $sidebarCollapsedValue.find('span').text('No');
- }
+ })
+ .catch(() => {
+ $loading.fadeOut();
});
- }
+ } else {
+ selected = $selectBox.find('input[type="hidden"]').val();
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].milestone_id = selected != null ? selected : null;
+ $loading.removeClass('hidden').fadeIn();
+ $dropdown.trigger('loading.gl.dropdown');
+ return $.ajax({
+ type: 'PUT',
+ url: issueUpdateURL,
+ data: data
+ }).done((data) => {
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.fadeOut();
+ $selectBox.hide();
+ $value.css('display', '');
+ if (data.milestone != null) {
+ data.milestone.full_path = this.currentProject.full_path;
+ data.milestone.remaining = timeFor(data.milestone.due_date);
+ data.milestone.name = data.milestone.title;
+ $value.html(milestoneLinkTemplate(data.milestone));
+ return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
+ } else {
+ $value.html(milestoneLinkNoneTemplate);
+ return $sidebarCollapsedValue.find('span').text('No');
+ }
+ });
}
- });
+ }
});
- }
-
- return MilestoneSelect;
- })();
-}).call(window);
+ });
+ }
+}
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 6e152497d20..a2f0a44863f 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -6,11 +6,12 @@ export default class NewCommitForm {
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
- this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
+ this.createMergeRequestContainer = form.find(
+ '.js-create-merge-request-container',
+ );
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
-
renderDestination() {
var different;
different = this.branchName.val() !== this.originalBranch.val();
@@ -23,6 +24,6 @@ export default class NewCommitForm {
this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
}
- return this.wasDifferent = different;
+ return (this.wasDifferent = different);
}
}
diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js
new file mode 100644
index 00000000000..f18f98b4e9a
--- /dev/null
+++ b/app/assets/javascripts/pages/users/show/index.js
@@ -0,0 +1,3 @@
+import UserCallout from '~/user_callout';
+
+export default () => new UserCallout();
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index 751a20991af..831aa92ac4f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,5 +1,6 @@
<script>
import tooltip from '../../vue_shared/directives/tooltip';
+ import icon from '../../vue_shared/components/icon.vue';
export default {
props: {
@@ -11,6 +12,9 @@
directives: {
tooltip,
},
+ components: {
+ icon,
+ },
};
</script>
<template>
@@ -24,10 +28,9 @@
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts">
- <i
- class="fa fa-download"
- aria-hidden="true">
- </i>
+ <icon
+ name="download">
+ </icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
diff --git a/app/assets/javascripts/repo/components/commit_sidebar/list.vue b/app/assets/javascripts/repo/components/commit_sidebar/list.vue
deleted file mode 100644
index fb862e7bf01..00000000000
--- a/app/assets/javascripts/repo/components/commit_sidebar/list.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script>
- import icon from '../../../vue_shared/components/icon.vue';
- import listItem from './list_item.vue';
- import listCollapsed from './list_collapsed.vue';
-
- export default {
- components: {
- icon,
- listItem,
- listCollapsed,
- },
- props: {
- title: {
- type: String,
- required: true,
- },
- fileList: {
- type: Array,
- required: true,
- },
- collapsed: {
- type: Boolean,
- required: true,
- },
- },
- methods: {
- toggleCollapsed() {
- this.$emit('toggleCollapsed');
- },
- },
- };
-</script>
-
-<template>
- <div class="multi-file-commit-panel-section">
- <header
- class="multi-file-commit-panel-header"
- :class="{
- 'is-collapsed': collapsed,
- }"
- >
- <icon
- name="list-bulleted"
- :size="18"
- css-classes="append-right-default"
- />
- <template v-if="!collapsed">
- {{ title }}
- <button
- type="button"
- class="btn btn-transparent multi-file-commit-panel-collapse-btn"
- @click="toggleCollapsed"
- >
- <i
- aria-hidden="true"
- class="fa fa-angle-double-right"
- >
- </i>
- </button>
- </template>
- </header>
- <div class="multi-file-commit-list">
- <list-collapsed
- v-if="collapsed"
- />
- <template v-else>
- <ul
- v-if="fileList.length"
- class="list-unstyled append-bottom-0"
- >
- <li
- v-for="file in fileList"
- :key="file.key"
- >
- <list-item
- :file="file"
- />
- </li>
- </ul>
- <div
- v-else
- class="help-block prepend-top-0"
- >
- No changes
- </div>
- </template>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/new_dropdown/index.vue b/app/assets/javascripts/repo/components/new_dropdown/index.vue
deleted file mode 100644
index 781404cf8ca..00000000000
--- a/app/assets/javascripts/repo/components/new_dropdown/index.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script>
- import { mapState } from 'vuex';
- import newModal from './modal.vue';
- import upload from './upload.vue';
- import icon from '../../../vue_shared/components/icon.vue';
-
- export default {
- components: {
- icon,
- newModal,
- upload,
- },
- data() {
- return {
- openModal: false,
- modalType: '',
- };
- },
- computed: {
- ...mapState([
- 'path',
- ]),
- },
- methods: {
- createNewItem(type) {
- this.modalType = type;
- this.toggleModalOpen();
- },
- toggleModalOpen() {
- this.openModal = !this.openModal;
- },
- },
- };
-</script>
-
-<template>
- <div>
- <ul class="breadcrumb repo-breadcrumb">
- <li class="dropdown">
- <button
- type="button"
- class="btn btn-default dropdown-toggle add-to-tree"
- data-toggle="dropdown"
- aria-label="Create new file or directory"
- >
- <icon
- name="plus"
- css-classes="pull-left"
- />
- <icon
- name="arrow-down"
- css-classes="pull-left"
- />
- </button>
- <ul class="dropdown-menu">
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('blob')"
- >
- {{ __('New file') }}
- </a>
- </li>
- <li>
- <upload
- :path="path"
- />
- </li>
- <li>
- <a
- href="#"
- role="button"
- @click.prevent="createNewItem('tree')"
- >
- {{ __('New directory') }}
- </a>
- </li>
- </ul>
- </li>
- </ul>
- <new-modal
- v-if="openModal"
- :type="modalType"
- :path="path"
- @toggle="toggleModalOpen"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
deleted file mode 100644
index a00e1e9d809..00000000000
--- a/app/assets/javascripts/repo/components/repo.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-<script>
-import { mapState, mapGetters } from 'vuex';
-import RepoSidebar from './repo_sidebar.vue';
-import RepoCommitSection from './repo_commit_section.vue';
-import RepoTabs from './repo_tabs.vue';
-import RepoFileButtons from './repo_file_buttons.vue';
-import RepoPreview from './repo_preview.vue';
-import repoEditor from './repo_editor.vue';
-
-export default {
- computed: {
- ...mapState([
- 'currentBlobView',
- ]),
- ...mapGetters([
- 'isCollapsed',
- 'changedFiles',
- ]),
- },
- components: {
- RepoSidebar,
- RepoTabs,
- RepoFileButtons,
- repoEditor,
- RepoCommitSection,
- RepoPreview,
- },
- mounted() {
- const returnValue = 'Are you sure you want to lose unsaved changes?';
- window.onbeforeunload = (e) => {
- if (!this.changedFiles.length) return undefined;
-
- Object.assign(e, {
- returnValue,
- });
- return returnValue;
- };
- },
-};
-</script>
-
-<template>
- <div
- class="multi-file"
- :class="{
- 'is-collapsed': isCollapsed
- }"
- >
- <repo-sidebar/>
- <div
- v-if="isCollapsed"
- class="multi-file-edit-pane"
- >
- <repo-tabs />
- <component
- class="multi-file-edit-pane-content"
- :is="currentBlobView"
- />
- <repo-file-buttons />
- </div>
- <repo-commit-section />
- </div>
-</template>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
deleted file mode 100644
index 4ea21913129..00000000000
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script>
-import { mapState, mapGetters, mapActions } from 'vuex';
-import RepoPreviousDirectory from './repo_prev_directory.vue';
-import RepoFile from './repo_file.vue';
-import RepoLoadingFile from './repo_loading_file.vue';
-
-export default {
- components: {
- 'repo-previous-directory': RepoPreviousDirectory,
- 'repo-file': RepoFile,
- 'repo-loading-file': RepoLoadingFile,
- },
- created() {
- window.addEventListener('popstate', this.popHistoryState);
- },
- destroyed() {
- window.removeEventListener('popstate', this.popHistoryState);
- },
- mounted() {
- this.getTreeData();
- },
- computed: {
- ...mapState([
- 'loading',
- 'isRoot',
- ]),
- ...mapState({
- projectName(state) {
- return state.project.name;
- },
- }),
- ...mapGetters([
- 'treeList',
- 'isCollapsed',
- ]),
- },
- methods: {
- ...mapActions([
- 'getTreeData',
- 'popHistoryState',
- ]),
- },
-};
-</script>
-
-<template>
-<div class="ide-file-list">
- <table class="table">
- <thead>
- <tr>
- <th
- v-if="isCollapsed"
- >
- </th>
- <template v-else>
- <th class="name multi-file-table-name">
- Name
- </th>
- <th class="hidden-sm hidden-xs last-commit">
- Last commit
- </th>
- <th class="hidden-xs last-update text-right">
- Last update
- </th>
- </template>
- </tr>
- </thead>
- <tbody>
- <repo-previous-directory
- v-if="!isRoot && treeList.length"
- />
- <repo-loading-file
- v-if="!treeList.length && loading"
- v-for="n in 5"
- :key="n"
- />
- <repo-file
- v-for="file in treeList"
- :key="file.key"
- :file="file"
- />
- </tbody>
- </table>
-</div>
-</template>
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
deleted file mode 100644
index b6801af7fcb..00000000000
--- a/app/assets/javascripts/repo/index.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import Vue from 'vue';
-import { mapActions } from 'vuex';
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
-import Repo from './components/repo.vue';
-import RepoEditButton from './components/repo_edit_button.vue';
-import newBranchForm from './components/new_branch_form.vue';
-import newDropdown from './components/new_dropdown/index.vue';
-import store from './stores';
-import Translate from '../vue_shared/translate';
-
-function initRepo(el) {
- if (!el) return null;
-
- return new Vue({
- el,
- store,
- components: {
- repo: Repo,
- },
- methods: {
- ...mapActions([
- 'setInitialData',
- ]),
- },
- created() {
- const data = el.dataset;
-
- this.setInitialData({
- project: {
- id: data.projectId,
- name: data.projectName,
- url: data.projectUrl,
- },
- endpoints: {
- rootEndpoint: data.url,
- newMergeRequestUrl: data.newMergeRequestUrl,
- rootUrl: data.rootUrl,
- },
- canCommit: convertPermissionToBoolean(data.canCommit),
- onTopOfBranch: convertPermissionToBoolean(data.onTopOfBranch),
- currentRef: data.ref,
- path: data.currentPath,
- currentBranch: data.currentBranch,
- isRoot: convertPermissionToBoolean(data.root),
- isInitialRoot: convertPermissionToBoolean(data.root),
- });
- },
- render(createElement) {
- return createElement('repo');
- },
- });
-}
-
-function initRepoEditButton(el) {
- return new Vue({
- el,
- store,
- components: {
- repoEditButton: RepoEditButton,
- },
- render(createElement) {
- return createElement('repo-edit-button');
- },
- });
-}
-
-function initNewDropdown(el) {
- return new Vue({
- el,
- store,
- components: {
- newDropdown,
- },
- render(createElement) {
- return createElement('new-dropdown');
- },
- });
-}
-
-function initNewBranchForm() {
- const el = document.querySelector('.js-new-branch-dropdown');
-
- if (!el) return null;
-
- return new Vue({
- el,
- components: {
- newBranchForm,
- },
- store,
- render(createElement) {
- return createElement('new-branch-form');
- },
- });
-}
-
-const repo = document.getElementById('repo');
-const editButton = document.querySelector('.editable-mode');
-const newDropdownHolder = document.querySelector('.js-new-dropdown');
-
-Vue.use(Translate);
-
-initRepo(repo);
-initRepoEditButton(editButton);
-initNewBranchForm();
-initNewDropdown(newDropdownHolder);
diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js
deleted file mode 100644
index af5dcf054ef..00000000000
--- a/app/assets/javascripts/repo/stores/actions.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import Vue from 'vue';
-import { visitUrl } from '../../lib/utils/url_utility';
-import flash from '../../flash';
-import service from '../services';
-import * as types from './mutation_types';
-
-export const redirectToUrl = (_, url) => visitUrl(url);
-
-export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
-
-export const closeDiscardPopup = ({ commit }) => commit(types.TOGGLE_DISCARD_POPUP, false);
-
-export const discardAllChanges = ({ commit, getters, dispatch }) => {
- const changedFiles = getters.changedFiles;
-
- changedFiles.forEach((file) => {
- commit(types.DISCARD_FILE_CHANGES, file);
-
- if (file.tempFile) {
- dispatch('closeFile', { file, force: true });
- }
- });
-};
-
-export const closeAllFiles = ({ state, dispatch }) => {
- state.openFiles.forEach(file => dispatch('closeFile', { file }));
-};
-
-export const toggleEditMode = ({ state, commit, getters, dispatch }, force = false) => {
- const changedFiles = getters.changedFiles;
-
- if (changedFiles.length && !force) {
- commit(types.TOGGLE_DISCARD_POPUP, true);
- } else {
- commit(types.TOGGLE_EDIT_MODE);
- commit(types.TOGGLE_DISCARD_POPUP, false);
- dispatch('toggleBlobView');
-
- if (!state.editMode) {
- dispatch('discardAllChanges');
- }
- }
-};
-
-export const toggleBlobView = ({ commit, state }) => {
- if (state.editMode) {
- commit(types.SET_EDIT_MODE);
- } else {
- commit(types.SET_PREVIEW_MODE);
- }
-};
-
-export const checkCommitStatus = ({ state }) => service.getBranchData(
- state.project.id,
- state.currentBranch,
-)
- .then((data) => {
- const { id } = data.commit;
-
- if (state.currentRef !== id) {
- return true;
- }
-
- return false;
- })
- .catch(() => flash('Error checking branch data. Please try again.'));
-
-export const commitChanges = ({ commit, state, dispatch, getters }, { payload, newMr }) =>
- service.commit(state.project.id, payload)
- .then((data) => {
- const { branch } = payload;
- if (!data.short_id) {
- flash(data.message);
- return;
- }
-
- const lastCommit = {
- commit_path: `${state.project.url}/commit/${data.id}`,
- commit: {
- message: data.message,
- authored_date: data.committed_date,
- },
- };
-
- flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
-
- if (newMr) {
- dispatch('redirectToUrl', `${state.endpoints.newMergeRequestUrl}${branch}`);
- } else {
- commit(types.SET_COMMIT_REF, data.id);
-
- getters.changedFiles.forEach((entry) => {
- commit(types.SET_LAST_COMMIT_DATA, {
- entry,
- lastCommit,
- });
- });
-
- dispatch('discardAllChanges');
- dispatch('closeAllFiles');
- dispatch('toggleEditMode');
-
- window.scrollTo(0, 0);
- }
- })
- .catch(() => flash('Error committing changes. Please try again.'));
-
-export const createTempEntry = ({ state, dispatch }, { name, type, content = '', base64 = false }) => {
- if (type === 'tree') {
- dispatch('createTempTree', name);
- } else if (type === 'blob') {
- dispatch('createTempFile', {
- tree: state,
- name,
- base64,
- content,
- });
- }
-};
-
-export const popHistoryState = ({ state, dispatch, getters }) => {
- const treeList = getters.treeList;
- const tree = treeList.find(file => file.url === state.previousUrl);
-
- if (!tree) return;
-
- if (tree.type === 'tree') {
- dispatch('toggleTreeOpen', { endpoint: tree.url, tree });
- }
-};
-
-export const scrollToTab = () => {
- Vue.nextTick(() => {
- const tabs = document.getElementById('tabs');
-
- if (tabs) {
- const tabEl = tabs.querySelector('.active .repo-tab');
-
- tabEl.focus();
- }
- });
-};
-
-export * from './actions/tree';
-export * from './actions/file';
-export * from './actions/branch';
diff --git a/app/assets/javascripts/repo/stores/actions/branch.js b/app/assets/javascripts/repo/stores/actions/branch.js
deleted file mode 100644
index 61d9a5af3e3..00000000000
--- a/app/assets/javascripts/repo/stores/actions/branch.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import service from '../../services';
-import * as types from '../mutation_types';
-import { pushState } from '../utils';
-
-// eslint-disable-next-line import/prefer-default-export
-export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
- state.project.id,
- {
- branch,
- ref: state.currentBranch,
- },
-).then(res => res.json())
-.then((data) => {
- const branchName = data.name;
- const url = location.href.replace(state.currentBranch, branchName);
-
- pushState(url);
-
- commit(types.SET_CURRENT_BRANCH, branchName);
-});
diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js
deleted file mode 100644
index 7c251e26bed..00000000000
--- a/app/assets/javascripts/repo/stores/actions/tree.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import { visitUrl } from '../../../lib/utils/url_utility';
-import { normalizeHeaders } from '../../../lib/utils/common_utils';
-import flash from '../../../flash';
-import service from '../../services';
-import * as types from '../mutation_types';
-import {
- pushState,
- setPageTitle,
- findEntry,
- createTemp,
- createOrMergeEntry,
-} from '../utils';
-
-export const getTreeData = (
- { commit, state, dispatch },
- { endpoint = state.endpoints.rootEndpoint, tree = state } = {},
-) => {
- commit(types.TOGGLE_LOADING, tree);
-
- service.getTreeData(endpoint)
- .then((res) => {
- const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
-
- setPageTitle(pageTitle);
-
- return res.json();
- })
- .then((data) => {
- const prevLastCommitPath = tree.lastCommitPath;
- if (!state.isInitialRoot) {
- commit(types.SET_ROOT, data.path === '/');
- }
-
- dispatch('updateDirectoryData', { data, tree });
- commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
- commit(types.SET_LAST_COMMIT_URL, { tree, url: data.last_commit_path });
- commit(types.TOGGLE_LOADING, tree);
-
- if (prevLastCommitPath !== null) {
- dispatch('getLastCommitData', tree);
- }
-
- pushState(endpoint);
- })
- .catch(() => {
- flash('Error loading tree data. Please try again.');
- commit(types.TOGGLE_LOADING, tree);
- });
-};
-
-export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
- if (tree.opened) {
- // send empty data to clear the tree
- const data = { trees: [], blobs: [], submodules: [] };
-
- pushState(tree.parentTreeUrl);
-
- commit(types.SET_PREVIOUS_URL, tree.parentTreeUrl);
- dispatch('updateDirectoryData', { data, tree });
- } else {
- commit(types.SET_PREVIOUS_URL, endpoint);
- dispatch('getTreeData', { endpoint, tree });
- }
-
- commit(types.TOGGLE_TREE_OPEN, tree);
-};
-
-export const clickedTreeRow = ({ commit, dispatch }, row) => {
- if (row.type === 'tree') {
- dispatch('toggleTreeOpen', {
- endpoint: row.url,
- tree: row,
- });
- } else if (row.type === 'submodule') {
- commit(types.TOGGLE_LOADING, row);
-
- visitUrl(row.url);
- } else if (row.type === 'blob' && row.opened) {
- dispatch('setFileActive', row);
- } else {
- dispatch('getFileData', row);
- }
-};
-
-export const createTempTree = ({ state, commit, dispatch }, name) => {
- let tree = state;
- const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
-
- dirNames.forEach((dirName) => {
- const foundEntry = findEntry(tree, 'tree', dirName);
-
- if (!foundEntry) {
- const tmpEntry = createTemp({
- name: dirName,
- path: tree.path,
- type: 'tree',
- level: tree.level !== undefined ? tree.level + 1 : 0,
- });
-
- commit(types.CREATE_TMP_TREE, {
- parent: tree,
- tmpEntry,
- });
- commit(types.TOGGLE_TREE_OPEN, tmpEntry);
-
- tree = tmpEntry;
- } else {
- tree = foundEntry;
- }
- });
-
- if (tree.tempFile) {
- dispatch('createTempFile', {
- tree,
- name: '.gitkeep',
- });
- }
-};
-
-export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
- if (tree.lastCommitPath === null || getters.isCollapsed) return;
-
- service.getTreeLastCommit(tree.lastCommitPath)
- .then((res) => {
- const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
-
- commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
-
- return res.json();
- })
- .then((data) => {
- data.forEach((lastCommit) => {
- const entry = findEntry(tree, lastCommit.type, lastCommit.file_name);
-
- if (entry) {
- commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
- }
- });
-
- dispatch('getLastCommitData', tree);
- })
- .catch(() => flash('Error fetching log data.'));
-};
-
-export const updateDirectoryData = ({ commit, state }, { data, tree }) => {
- const level = tree.level !== undefined ? tree.level + 1 : 0;
- const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
- const createEntry = (entry, type) => createOrMergeEntry({
- tree,
- entry,
- level,
- type,
- parentTreeUrl,
- });
-
- const formattedData = [
- ...data.trees.map(t => createEntry(t, 'tree')),
- ...data.submodules.map(m => createEntry(m, 'submodule')),
- ...data.blobs.map(b => createEntry(b, 'blob')),
- ];
-
- commit(types.SET_DIRECTORY_DATA, { tree, data: formattedData });
-};
diff --git a/app/assets/javascripts/repo/stores/getters.js b/app/assets/javascripts/repo/stores/getters.js
deleted file mode 100644
index 5ce9f449905..00000000000
--- a/app/assets/javascripts/repo/stores/getters.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import _ from 'underscore';
-
-/*
- Takes the multi-dimensional tree and returns a flattened array.
- This allows for the table to recursively render the table rows but keeps the data
- structure nested to make it easier to add new files/directories.
-*/
-export const treeList = (state) => {
- const mapTree = arr => (!arr.tree.length ? [] : _.map(arr.tree, a => [a, mapTree(a)]));
-
- return _.chain(state.tree)
- .map(arr => [arr, mapTree(arr)])
- .flatten()
- .value();
-};
-
-export const changedFiles = state => state.openFiles.filter(file => file.changed);
-
-export const activeFile = state => state.openFiles.find(file => file.active);
-
-export const activeFileExtension = (state) => {
- const file = activeFile(state);
- return file ? `.${file.path.split('.').pop()}` : '';
-};
-
-export const isCollapsed = state => !!state.openFiles.length;
-
-export const canEditFile = (state) => {
- const currentActiveFile = activeFile(state);
- const openedFiles = state.openFiles;
-
- return state.canCommit &&
- state.onTopOfBranch &&
- openedFiles.length &&
- (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
-};
-
-export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
-
-export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
diff --git a/app/assets/javascripts/repo/stores/mutations/branch.js b/app/assets/javascripts/repo/stores/mutations/branch.js
deleted file mode 100644
index d8229e8a620..00000000000
--- a/app/assets/javascripts/repo/stores/mutations/branch.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as types from '../mutation_types';
-
-export default {
- [types.SET_CURRENT_BRANCH](state, currentBranch) {
- Object.assign(state, {
- currentBranch,
- });
- },
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index 13e4cb5717e..85bfd03a3cf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -1,5 +1,6 @@
import tooltip from '../../vue_shared/directives/tooltip';
import { pluralize } from '../../lib/utils/text_utility';
+import icon from '../../vue_shared/components/icon.vue';
export default {
name: 'MRWidgetHeader',
@@ -9,6 +10,9 @@ export default {
directives: {
tooltip,
},
+ components: {
+ icon,
+ },
computed: {
shouldShowCommitsBehindText() {
return this.mr.divergedCommitsCount > 0;
@@ -81,10 +85,9 @@ export default {
data-toggle="dropdown"
aria-label="Download as"
role="button">
- <i
- class="fa fa-download"
- aria-hidden="true">
- </i>
+ <icon
+ name="download">
+ </icon>
<i
class="fa fa-caret-down"
aria-hidden="true">
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 52814de8b2d..59ca9a0a6d4 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -2,13 +2,14 @@
import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip';
+ import Icon from '../../vue_shared/components/icon.vue';
export default {
props: {
/**
* Indicates the existance of a tag.
* Used to render the correct icon, if true will render `fa-tag` icon,
- * if false will render `fa-code-fork` icon.
+ * if false will render a svg sprite fork icon
*/
tag: {
type: Boolean,
@@ -107,6 +108,7 @@
},
components: {
userAvatarLink,
+ Icon,
},
created() {
this.commitIconSvg = commitIconSvg;
@@ -123,11 +125,10 @@
class="fa fa-tag"
aria-hidden="true">
</i>
- <i
+ <icon
v-if="!tag"
- class="fa fa-code-fork"
- aria-hidden="true">
- </i>
+ name="fork">
+ </icon>
</div>
<a
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
new file mode 100644
index 00000000000..dce23bd65f6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -0,0 +1,103 @@
+<script>
+
+/* This is a re-usable vue component for rendering a project avatar that
+ does not need to link to the project's profile. The image and an optional
+ tooltip can be configured by props passed to this component.
+
+ Sample configuration:
+
+ <project-avatar-image
+ :lazy="true"
+ :img-src="projectAvatarSrc"
+ :img-alt="tooltipText"
+ :tooltip-text="tooltipText"
+ tooltip-placement="top"
+ />
+
+*/
+
+import defaultAvatarUrl from 'images/no_avatar.png';
+import { placeholderImage } from '../../../lazy_loader';
+import tooltip from '../../directives/tooltip';
+
+export default {
+ name: 'ProjectAvatarImage',
+ props: {
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ imgSrc: {
+ type: String,
+ required: false,
+ default: defaultAvatarUrl,
+ },
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ imgAlt: {
+ type: String,
+ required: false,
+ default: 'project avatar',
+ },
+ size: {
+ type: Number,
+ required: false,
+ default: 20,
+ },
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ },
+ directives: {
+ tooltip,
+ },
+ computed: {
+ // API response sends null when gravatar is disabled and
+ // we provide an empty string when we use it inside project avatar link.
+ // In both cases we should render the defaultAvatarUrl
+ sanitizedSource() {
+ return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ },
+ resultantSrcAttribute() {
+ return this.lazy ? placeholderImage : this.sanitizedSource;
+ },
+ tooltipContainer() {
+ return this.tooltipText ? 'body' : null;
+ },
+ avatarSizeClass() {
+ return `s${this.size}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <img
+ v-tooltip
+ class="avatar"
+ :class="{
+ lazy,
+ [avatarSizeClass]: true,
+ [cssClasses]: true
+ }"
+ :src="resultantSrcAttribute"
+ :width="size"
+ :height="size"
+ :alt="imgAlt"
+ :data-src="sanitizedSource"
+ :data-container="tooltipContainer"
+ :data-placement="tooltipPlacement"
+ :title="tooltipText"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 710452bb3d3..33096b53cf8 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -122,11 +122,17 @@ export default {
return items;
},
+ showPagination() {
+ return this.pageInfo.totalPages > 1;
+ },
},
};
</script>
<template>
- <div class="gl-pagination">
+ <div
+ v-if="showPagination"
+ class="gl-pagination"
+ >
<ul class="pagination clearfix">
<li
v-for="item in getItems"