From 209fc1f45090756edfbc3bd582a04fdaf1995844 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 8 Mar 2024 11:50:38 +0100 Subject: [PATCH] feat(filter): autocomplete for assignees --- .../project/partials/FilterInput.vue | 49 +++++++++++++++---- .../components/project/partials/filters.vue | 15 +++++- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue index 357a12adc..0adb9085d 100644 --- a/frontend/src/components/project/partials/FilterInput.vue +++ b/frontend/src/components/project/partials/FilterInput.vue @@ -8,28 +8,33 @@ import {createRandomID} from '@/helpers/randomId' import AutocompleteDropdown from '@/components/input/AutocompleteDropdown.vue' import {useLabelStore} from '@/stores/labels' import XLabel from '@/components/tasks/partials/label.vue' +import User from '@/components/misc/user.vue' +import ProjectUserService from '@/services/projectUsers' const { - modelValue, + projectId, } = defineProps<{ - modelValue: string, + projectId?: number, }>() -const filterQuery = ref('') +const model = defineModel() + +const filterQuery = ref('') const { textarea: filterInput, height, } = useAutoHeightTextarea(filterQuery) watch( - () => modelValue, + () => model.value, () => { - filterQuery.value = modelValue + filterQuery.value = model.value }, {immediate: true}, ) const userService = new UserService() +const projectUserService = new ProjectUserService() const dateFields = [ 'dueDate', @@ -47,6 +52,11 @@ const labelFields = [ 'labels', ] +const autocompleteFields = [ + ...labelFields, + ...assigneeFields, +] + const availableFilterFields = [ 'done', 'priority', @@ -210,15 +220,27 @@ 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') + autocompleteFields.forEach(field => { + const pattern = new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&\|\(\)]+\\1?)?$', 'ig') const match = pattern.exec(textUpToCursor) if (match !== null) { const [matched, prefix, operator, space, keyword] = match if (keyword) { - autocompleteResultType.value = 'labels' - autocompleteResults.value = labelStore.filterLabelsByQuery([], keyword) + if (matched.startsWith('label')) { + autocompleteResultType.value = 'labels' + autocompleteResults.value = labelStore.filterLabelsByQuery([], keyword) + } + if (matched.startsWith('assignee')) { + autocompleteResultType.value = 'assignees' + if (projectId) { + projectUserService.getAll({projectId}, {s: keyword}) + .then(users => autocompleteResults.value = users.length > 1 ? users : []) + } else { + userService.getAll({}, {s: keyword}) + .then(users => autocompleteResults.value = users.length > 1 ? users : []) + } + } autocompleteMatchText.value = keyword autocompleteMatchPosition.value = prefix.length - 1 } @@ -228,7 +250,9 @@ function handleFieldInput(e, autocompleteOnInput) { function autocompleteSelect(value) { filterQuery.value = filterQuery.value.substring(0, autocompleteMatchPosition.value + 1) + - value.title + + (autocompleteResultType.value === 'labels' + ? value.title + : value.username) + filterQuery.value.substring(autocompleteMatchPosition.value + autocompleteMatchText.value.length + 1) autocompleteResults.value = [] @@ -281,6 +305,11 @@ function autocompleteSelect(value) { v-if="autocompleteResultType === 'labels'" :label="item" /> + diff --git a/frontend/src/components/project/partials/filters.vue b/frontend/src/components/project/partials/filters.vue index 29535bf20..08113cdb8 100644 --- a/frontend/src/components/project/partials/filters.vue +++ b/frontend/src/components/project/partials/filters.vue @@ -31,7 +31,10 @@ - +
@@ -231,6 +234,7 @@ import ProjectService from '@/services/project' // FIXME: do not use this here for now. instead create new version from DEFAULT_PARAMS import {getDefaultParams} from '@/composables/useTaskList' import FilterInput from '@/components/project/partials/FilterInput.vue' +import {useRoute} from 'vue-router' const props = defineProps({ modelValue: { @@ -244,6 +248,15 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue']) +const route = useRoute() +const projectId = computed(() => { + if (route.name?.startsWith('project.')) { + return Number(route.params.projectId) + } + + return undefined +}) + // FIXME: merge with DEFAULT_PARAMS in taskProject.js const DEFAULT_PARAMS = { sort_by: [],