summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue167
-rw-r--r--app/assets/javascripts/ide/components/file_finder/item.vue50
-rw-r--r--app/assets/javascripts/ide/components/ide.vue13
-rw-r--r--app/assets/javascripts/ide/stores/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/getters.js10
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js5
-rw-r--r--app/assets/javascripts/ide/stores/state.js2
-rw-r--r--app/assets/stylesheets/pages/repo.scss1
9 files changed, 194 insertions, 59 deletions
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index ebcd6f7592b..d1902557fe8 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -1,8 +1,11 @@
<script>
-import { mapGetters, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import VirtualList from 'vue-virtual-scroll-list';
import Item from './item.vue';
+import router from '../../ide_router';
+
+const MAX_RESULTS = 20;
export default {
components: {
@@ -11,89 +14,153 @@ export default {
},
data() {
return {
+ focusedIndex: 0,
searchText: '',
};
},
computed: {
...mapGetters(['allBlobs']),
- ...mapState(['loading']),
+ ...mapState(['fileFindVisible', 'loading']),
filteredBlobs() {
const searchText = this.searchText.trim();
- if (searchText === '') return this.allBlobs;
+ if (searchText === '') return this.allBlobs.slice(0, MAX_RESULTS);
return fuzzaldrinPlus.filter(this.allBlobs, searchText, {
key: 'path',
+ maxResults: MAX_RESULTS,
});
},
listShowCount() {
- if (this.filteredBlobs.length === 0) return 1;
+ if (!this.filteredBlobs.length) return 1;
return this.filteredBlobs.length > 5 ? 5 : this.filteredBlobs.length;
},
listHeight() {
- return this.listShowCount > 1 ? 55 : 33;
+ return this.filteredBlobs.length ? 55 : 33;
+ },
+ },
+ watch: {
+ fileFindVisible() {
+ this.$nextTick(() => this.$refs.searchInput.focus());
+ },
+ searchText() {
+ if (this.searchText.trim() !== '') {
+ this.focusedIndex = 0;
+ }
},
},
- mounted() {
- this.$refs.searchInput.focus();
+ methods: {
+ ...mapActions(['toggleFileFinder']),
+ onKeydown(e) {
+ switch (e.keyCode) {
+ case 38:
+ // UP
+ e.preventDefault();
+ if (this.focusedIndex > 0) this.focusedIndex -= 1;
+ break;
+ case 40:
+ // DOWN
+ e.preventDefault();
+ if (this.focusedIndex < this.filteredBlobs.length - 1) this.focusedIndex += 1;
+ break;
+ default:
+ break;
+ }
+ },
+ onKeyup(e) {
+ switch (e.keyCode) {
+ case 13:
+ // ENTER
+ this.openFile(this.filteredBlobs[this.focusedIndex]);
+ break;
+ default:
+ break;
+ }
+ },
+ openFile(file) {
+ this.toggleFileFinder(false);
+ router.push(`/project${file.url}`);
+ },
},
};
</script>
<template>
- <div class="dropdown-menu diff-file-changes ide-file-finder" style="display: block;">
- <div class="dropdown-input">
- <input
- type="search"
- class="dropdown-input-field"
- placeholder="Search files"
- autocomplete="off"
- v-model="searchText"
- ref="searchInput"
- />
- <i
- aria-hidden="true"
- class="fa fa-search dropdown-input-search"
- ></i>
- </div>
- <div>
- <virtual-list
- :size="listHeight"
- :remain="listShowCount"
- wtag="ul"
- >
- <template v-if="filteredBlobs.length">
+ <div
+ class="ide-file-finder-overlay"
+ @click.self="toggleFileFinder(false)"
+ >
+ <div
+ class="dropdown-menu diff-file-changes ide-file-finder show"
+ >
+ <div class="dropdown-input">
+ <input
+ type="search"
+ class="dropdown-input-field"
+ placeholder="Search files"
+ autocomplete="off"
+ v-model="searchText"
+ ref="searchInput"
+ @keydown="onKeydown($event)"
+ @keyup="onKeyup($event)"
+ />
+ <i
+ aria-hidden="true"
+ class="fa fa-search dropdown-input-search"
+ ></i>
+ </div>
+ <div>
+ <virtual-list
+ :size="listHeight"
+ :remain="listShowCount"
+ :start="focusedIndex"
+ wtag="ul"
+ >
+ <template v-if="filteredBlobs.length">
+ <li
+ v-for="(file, index) in filteredBlobs"
+ :key="file.key"
+ >
+ <item
+ :file="file"
+ :search-text="searchText"
+ :focused="index === focusedIndex"
+ @click="openFile"
+ />
+ </li>
+ </template>
<li
- v-for="file in filteredBlobs"
- :key="file.key"
+ v-else
+ class="dropdown-menu-empty-itemhidden"
>
- <item
- :file="file"
- />
+ <a href="">
+ <template v-if="loading">
+ Loading...
+ </template>
+ <template v-else>
+ No files found.
+ </template>
+ </a>
</li>
- </template>
- <li
- v-else
- class="dropdown-menu-empty-itemhidden"
- >
- <a href="">
- <template v-if="loading">
- Loading...
- </template>
- <template v-else>
- No files found.
- </template>
- </a>
- </li>
- </virtual-list>
+ </virtual-list>
+ </div>
</div>
</div>
</template>
<style>
+.ide-file-finder-overlay {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 100;
+}
+
.ide-file-finder {
- top: 100px;
+ top: 10px;
left: 50%;
transform: translateX(-50%);
}
diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue
index 482bfb96d93..4003f72f1ac 100644
--- a/app/assets/javascripts/ide/components/file_finder/item.vue
+++ b/app/assets/javascripts/ide/components/file_finder/item.vue
@@ -1,4 +1,5 @@
<script>
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
import FileIcon from '../../../vue_shared/components/file_icon.vue';
export default {
@@ -10,14 +11,42 @@ export default {
type: Object,
required: true,
},
+ focused: {
+ type: Boolean,
+ required: true,
+ },
+ searchText: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ clickRow() {
+ this.$emit('click', this.file);
+ },
+ highlightText(text) {
+ const occurrences = fuzzaldrinPlus.match(text, this.searchText);
+
+ return text
+ .split('')
+ .map(
+ (char, i) =>
+ `<span class="${occurrences.indexOf(i) >= 0 ? 'highlighted' : ''}">${char}</span>`,
+ )
+ .join('');
+ },
},
};
</script>
<template>
<a
- href=""
+ href="#"
class="diff-changed-file"
+ :class="{
+ 'is-focused': focused,
+ }"
+ @click.prevent="clickRow"
>
<file-icon
:file-name="file.name"
@@ -25,12 +54,23 @@ export default {
css-classes="diff-file-changed-icon append-right-8"
/>
<span class="diff-changed-file-content append-right-8">
- <strong class="diff-changed-file-name">
- {{ file.name }}
+ <strong
+ class="diff-changed-file-name"
+ v-html="highlightText(this.file.name)"
+ >
</strong>
- <span class="diff-changed-file-path prepend-top-5">
- {{ file.path }}
+ <span
+ class="diff-changed-file-path prepend-top-5"
+ v-html="highlightText(this.file.path)"
+ >
</span>
</span>
</a>
</template>
+
+<style>
+.highlighted {
+ color: #1b69b6;
+ font-weight: 600;
+}
+</style>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 8489639e3b0..065edd81d2c 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,5 +1,6 @@
<script>
-import { mapState, mapGetters } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
+import Mousetrap from 'mousetrap';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
@@ -50,6 +51,14 @@ export default {
});
return returnValue;
};
+
+ Mousetrap.bind('t', e => {
+ e.preventDefault();
+ this.toggleFileFinder(true);
+ });
+ },
+ methods: {
+ ...mapActions(['toggleFileFinder']),
},
};
</script>
@@ -59,7 +68,7 @@ export default {
class="ide-view"
>
<find-file
- v-if="fileFindVisible"
+ v-show="fileFindVisible"
/>
<ide-sidebar />
<div
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index c6ba679d99c..57420b6c699 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -112,6 +112,9 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
};
+export const toggleFileFinder = ({ commit }, fileFindVisible) =>
+ commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
+
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 6776be3b77c..07f7e73d8e9 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -37,4 +37,12 @@ export const hasChanges = state => !!state.changedFiles.length;
export const hasMergeRequest = state => !!state.currentMergeRequestId;
export const allBlobs = state =>
- Object.keys(state.entries).reduce((acc, key) => acc.concat(state.entries[key]), []);
+ Object.keys(state.entries).reduce((acc, key) => {
+ const entry = state.entries[key];
+
+ if (entry.type === 'blob') {
+ acc.push(entry);
+ }
+
+ return acc;
+ }, []);
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index e3f504e5ab0..d64908ea971 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -53,3 +53,5 @@ export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
+
+export const TOGGLE_FILE_FINDER = 'SHOW_FILE_FINDER';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 5e5eb831662..4510ef64262 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -95,6 +95,11 @@ export default {
delayViewerUpdated,
});
},
+ [types.TOGGLE_FILE_FINDER](state, fileFindVisible) {
+ Object.assign(state, {
+ fileFindVisible,
+ });
+ },
...projectMutations,
...mergeRequestMutation,
...fileMutations,
diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index e976884ae1f..3694ad953fa 100644
--- a/app/assets/javascripts/ide/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -17,5 +17,5 @@ export default () => ({
entries: {},
viewer: 'editor',
delayViewerUpdated: false,
- fileFindVisible: true,
+ fileFindVisible: false,
});
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 5f46e69a56d..340bba7ef4c 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -17,6 +17,7 @@
}
.ide-view {
+ position: relative;
display: flex;
height: calc(100vh - #{$header-height});
margin-top: 0;