diff options
Diffstat (limited to 'app/assets/javascripts/projects/project_find_file.js')
-rw-r--r-- | app/assets/javascripts/projects/project_find_file.js | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/app/assets/javascripts/projects/project_find_file.js b/app/assets/javascripts/projects/project_find_file.js new file mode 100644 index 00000000000..d295c06928f --- /dev/null +++ b/app/assets/javascripts/projects/project_find_file.js @@ -0,0 +1,183 @@ +/* eslint-disable func-names, consistent-return, no-return-assign */ + +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import $ from 'jquery'; +import createFlash from '~/flash'; +import { sanitize } from '~/lib/dompurify'; +import axios from '~/lib/utils/axios_utils'; +import { spriteIcon } from '~/lib/utils/common_utils'; +import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; + +// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> ) +const highlighter = function (element, text, matches) { + let j = 0; + let len = 0; + let lastIndex = 0; + let matchedChars = []; + let matchIndex = matches[j]; + let unmatched = text.substring(lastIndex, matchIndex); + for (j = 0, len = matches.length; j < len; j += 1) { + matchIndex = matches[j]; + unmatched = text.substring(lastIndex, matchIndex); + if (unmatched) { + if (matchedChars.length) { + element.append(matchedChars.join('').bold()); + } + matchedChars = []; + element.append(document.createTextNode(unmatched)); + } + matchedChars.push(text[matchIndex]); + lastIndex = matchIndex + 1; + } + if (matchedChars.length) { + element.append(matchedChars.join('').bold()); + } + return element.append(document.createTextNode(text.substring(lastIndex))); +}; + +export default class ProjectFindFile { + constructor(element1, options) { + this.element = element1; + this.options = options; + this.goToBlob = this.goToBlob.bind(this); + this.goToTree = this.goToTree.bind(this); + this.selectRowDown = this.selectRowDown.bind(this); + this.selectRowUp = this.selectRowUp.bind(this); + this.filePaths = {}; + this.inputElement = this.element.find('.file-finder-input'); + // init event + this.initEvent(); + // focus text input box + this.inputElement.focus(); + // load file list + this.load(this.options.url); + } + + initEvent() { + // eslint-disable-next-line @gitlab/no-global-event-off + this.inputElement.off('keyup'); + this.inputElement.on('keyup', (event) => { + const target = $(event.target); + const value = target.val(); + const ref = target.data('oldValue'); + const oldValue = ref != null ? ref : ''; + if (value !== oldValue) { + target.data('oldValue', value); + this.findFile(); + return this.element.find('tr.tree-item').eq(0).addClass('selected').focus(); + } + }); + } + + findFile() { + const searchText = sanitize(this.inputElement.val()); + const result = + searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths; + return this.renderList(result, searchText); + // find file + } + + // files paths load + load(url) { + axios + .get(url) + .then(({ data }) => { + this.element.find('.loading').hide(); + this.filePaths = data; + this.findFile(); + this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus(); + }) + .catch(() => + createFlash({ + message: __('An error occurred while loading filenames'), + }), + ); + } + + // render result + renderList(filePaths, searchText) { + let i = 0; + let len = 0; + let matches = []; + const results = []; + this.element.find('.tree-table > tbody').empty(); + for (i = 0, len = filePaths.length; i < len; i += 1) { + const filePath = filePaths[i]; + if (i === 20) { + break; + } + if (searchText) { + matches = fuzzaldrinPlus.match(filePath, searchText); + } + const blobItemUrl = joinPaths(this.options.blobUrlTemplate, escapeFileUrl(filePath)); + const html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl); + results.push(this.element.find('.tree-table > tbody').append(html)); + } + + this.element.find('.empty-state').toggleClass('hidden', Boolean(results.length)); + + return results; + } + + // make tbody row html + static makeHtml(filePath, matches, blobItemUrl) { + const $tr = $( + `<tr class='tree-item'><td class='tree-item-file-name link-container'><a>${spriteIcon( + 'doc-text', + 's16 vertical-align-middle gl-mr-1', + )}<span class='str-truncated'></span></a></td></tr>`, + ); + if (matches) { + $tr + .find('a') + .replaceWith(highlighter($tr.find('a'), filePath, matches).attr('href', blobItemUrl)); + } else { + $tr.find('a').attr('href', blobItemUrl); + $tr.find('.str-truncated').text(filePath); + } + return $tr; + } + + selectRow(type) { + const rows = this.element.find('.files-slider tr.tree-item'); + let selectedRow = this.element.find('.files-slider tr.tree-item.selected'); + let next = selectedRow.prev(); + if (rows && rows.length > 0) { + if (selectedRow && selectedRow.length > 0) { + if (type === 'UP') { + next = selectedRow.prev(); + } else if (type === 'DOWN') { + next = selectedRow.next(); + } + if (next.length > 0) { + selectedRow.removeClass('selected'); + selectedRow = next; + } + } else { + selectedRow = rows.eq(0); + } + return selectedRow.addClass('selected').focus(); + } + } + + selectRowUp() { + return this.selectRow('UP'); + } + + selectRowDown() { + return this.selectRow('DOWN'); + } + + goToTree() { + return (window.location.href = this.options.treeUrl); + } + + goToBlob() { + const $link = this.element.find('.tree-item.selected .tree-item-file-name a'); + + if ($link.length) { + $link.get(0).click(); + } + } +} |