From f6fc50de03debdc9b26078b828dc6d0c66507bff Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 6 Mar 2024 17:59:00 +0100 Subject: [PATCH] feat(filter): add autocompletion poc for labels --- .../components/input/AutocompleteDropdown.vue | 237 ++++++++++++++++++ .../project/partials/FilterInput.vue | 95 +++++-- 2 files changed, 307 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/input/AutocompleteDropdown.vue diff --git a/frontend/src/components/input/AutocompleteDropdown.vue b/frontend/src/components/input/AutocompleteDropdown.vue new file mode 100644 index 000000000..096ee09c7 --- /dev/null +++ b/frontend/src/components/input/AutocompleteDropdown.vue @@ -0,0 +1,237 @@ + + + diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue index 667564db4..06b32a684 100644 --- a/frontend/src/components/project/partials/FilterInput.vue +++ b/frontend/src/components/project/partials/FilterInput.vue @@ -5,6 +5,7 @@ import DatepickerWithValues from '@/components/date/datepickerWithValues.vue' import UserService from '@/services/user' import {getAvatarUrl, getDisplayName} from '@/models/user' import {createRandomID} from '@/helpers/randomId' +import AutocompleteDropdown from '@/components/input/AutocompleteDropdown.vue' const { modelValue, @@ -40,14 +41,18 @@ const assigneeFields = [ 'assignees', ] +const labelFields = [ + 'labels', +] + const availableFilterFields = [ 'done', 'priority', 'usePriority', 'percentDone', - 'labels', ...dateFields, ...assigneeFields, + ...labelFields, ] const filterOperators = [ @@ -69,6 +74,9 @@ const filterJoinOperators = [ ')', ] +const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=)' +const FILTER_JOIN_OPERATORS_REGEX = '(&&|\|\||\(|\))' + function escapeHtml(unsafe: string): string { return unsafe .replace(/&/g, '&') @@ -91,7 +99,7 @@ const highlightedFilterQuery = computed(() => { let highlighted = escapeHtml(filterQuery.value) dateFields .forEach(o => { - const pattern = new RegExp(o + '(\\s*)(<|>|<=|>=|=|!=)(\\s*)([\'"]?)([^\'"\\s]+\\1?)?', 'ig') + const pattern = new RegExp(o + '(\\s*)' + FILTER_OPERATORS_REGEX + '(\\s*)([\'"]?)([^\'"\\s]+\\1?)?', 'ig') highlighted = highlighted.replaceAll(pattern, (match, spacesBefore, token, spacesAfter, start, value, position) => { if (typeof value === 'undefined') { value = '' @@ -102,7 +110,7 @@ const highlightedFilterQuery = computed(() => { }) assigneeFields .forEach(f => { - const pattern = new RegExp(f + '\\s*(<|>|<=|>=|=|!=)\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig') + const pattern = new RegExp(f + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig') highlighted = highlighted.replaceAll(pattern, (match, token, start, value) => { if (typeof value === 'undefined') { value = '' @@ -189,33 +197,70 @@ function updateDateInQuery(newDate: string) { currentOldDatepickerValue.value = newDate filterQuery.value = unEscapeHtml(escaped) } + +function handleFieldInput(e, autocompleteOnInput) { + const cursorPosition = filterInput.value.selectionStart + const textUpToCursor = filterQuery.value.substring(0, cursorPosition) + + labelFields.forEach(l => { + const pattern = new RegExp('(' + l + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&\|\(\)]+\\1?)?$', 'ig') + const match = pattern.exec(textUpToCursor) + + if (match !== null) { + const [matched, prefix, operator, space, keyword] = match + if (keyword) { + autocompleteResults.value = ['loool', keyword] + } + } + }) +} + +const autocompleteResults = ref([])