diff options
-rw-r--r-- | app/assets/javascripts/ide/components/file_finder/index.vue | 167 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/file_finder/item.vue | 50 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/ide.vue | 13 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/actions.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/getters.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/mutation_types.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/mutations.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/ide/stores/state.js | 2 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/repo.scss | 1 |
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; |