1
0
Fork 0

Compare commits

...

18 Commits

Author SHA1 Message Date
kolaente 7e50b6e270
fix: lint 2023-05-31 16:22:02 +02:00
kolaente 4aad488707 chore: group return parameter 2023-05-31 14:18:58 +00:00
kolaente 61f0f5210a chore: make fuzzy matching a paramater 2023-05-31 14:18:58 +00:00
kolaente 8ea27a6655 fix: make type singular 2023-05-31 14:18:58 +00:00
kolaente 4993673d5c chore(i18n): clarify translation string 2023-05-31 14:18:58 +00:00
kolaente 1510807164 chore: use startsWith for prefix matching 2023-05-31 14:18:58 +00:00
kolaente a99eb820eb fix: clarify user search setting 2023-05-31 14:18:58 +00:00
kolaente ef65853278 chore: remove user margin from the component 2023-05-31 14:18:58 +00:00
kolaente 0bb6085fb1 chore: remove user margin from the component 2023-05-31 14:18:58 +00:00
kolaente ede1069577 feat(quick add magic): allow fuzzy matching of assignees when the api results are unambigous 2023-05-31 14:18:58 +00:00
kolaente 15a5aedcf9 fix: ensure all matched quick add magic parts are correctly removed from the task 2023-05-31 14:18:58 +00:00
kolaente 5f29624414 fix: lint 2023-05-31 14:18:58 +00:00
kolaente 502241a173 feat(assignees): show user avatar in search results 2023-05-31 14:18:58 +00:00
kolaente 7df5a0ba2e feat: show initial list of users when opening the assignees view 2023-05-31 14:18:58 +00:00
kolaente 1d05675cbd chore: clarify users when can still be found even if they disabled it 2023-05-31 14:18:58 +00:00
kolaente 0fcf949653 fix(quick add magic): cleanup all assignee properties 2023-05-31 14:18:58 +00:00
kolaente 22c10d935a fix(quick add magic): use the project user service to find assignees for quick add magic 2023-05-31 14:18:58 +00:00
kolaente 3d0753ebc8 fix(quick add magic): don't replace the prefix in every occurrence when it is present in the matched part 2023-05-31 14:18:58 +00:00
8 changed files with 66 additions and 32 deletions

View File

@ -48,7 +48,6 @@ const displayName = computed(() => getDisplayName(props.user))
<style lang="scss" scoped>
.user {
margin: .5rem;
display: flex;
justify-items: center;

View File

@ -20,7 +20,8 @@
:user="n.notification.doer"
:show-username="false"
:avatar-size="16"
v-if="n.notification.doer"/>
v-if="n.notification.doer"
/>
<div class="detail">
<div>
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">

View File

@ -12,12 +12,15 @@
>
<template #tag="{item: user}">
<span class="assignee">
<user :avatar-size="32" :show-username="false" :user="user"/>
<user :avatar-size="32" :show-username="false" :user="user" class="m-2"/>
<BaseButton @click="removeAssignee(user)" class="remove-assignee" v-if="!disabled">
<icon icon="times"/>
</BaseButton>
</span>
</template>
<template #searchResult="{option: user}">
<user :avatar-size="24" :show-username="true" :user="user"/>
</template>
</Multiselect>
</template>
@ -104,11 +107,6 @@ async function removeAssignee(user: IUser) {
}
async function findUser(query: string) {
if (query === '') {
foundUsers.value = []
return
}
const response = await projectUserService.getAll({projectId: props.projectId}, {s: query}) as IUser[]
// Filter the results to not include users who are already assigned

View File

@ -56,6 +56,7 @@
:key="task.id + 'assignee' + a.id + i"
:show-username="false"
:user="a"
class="m-2"
/>
<!-- FIXME: use popup -->

View File

@ -76,8 +76,8 @@
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me a summary of my undone overdue tasks every day",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"discoverableByName": "Allow other users to add me as a member to teams or projects when they search for my name",
"discoverableByEmail": "Allow other users to add me as a member to teams or projects when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",

View File

@ -691,6 +691,14 @@ describe('Parse Task Text', () => {
expect(result.assignees).toHaveLength(1)
expect(result.assignees[0]).toBe('today')
})
it('should recognize an email address', () => {
const text = 'Lorem Ipsum @email@example.com'
const result = parseTaskText(text)
expect(result.text).toBe('Lorem Ipsum @email@example.com')
expect(result.assignees).toHaveLength(1)
expect(result.assignees[0]).toBe('email@example.com')
})
})
describe('Recurring Dates', () => {

View File

@ -109,7 +109,9 @@ const getItemsFromPrefix = (text: string, prefix: string): string[] => {
return
}
p = p.replace(prefix, '')
if (p.startsWith(prefix)) {
p = p.substring(1)
}
let itemText
if (p.charAt(0) === '\'') {
@ -120,8 +122,8 @@ const getItemsFromPrefix = (text: string, prefix: string): string[] => {
// Only until the next space
itemText = p.split(' ')[0]
}
if(itemText !== '') {
if (itemText !== '') {
items.push(itemText)
}
})
@ -278,13 +280,16 @@ const getRepeats = (text: string): repeatParsedResult => {
export const cleanupItemText = (text: string, items: string[], prefix: string): string => {
items.forEach(l => {
if (l === '') {
return
}
text = text
.replace(`${prefix}'${l}' `, '')
.replace(`${prefix}'${l}'`, '')
.replace(`${prefix}"${l}" `, '')
.replace(`${prefix}"${l}"`, '')
.replace(`${prefix}${l} `, '')
.replace(`${prefix}${l}`, '')
.replace(new RegExp(`\\${prefix}'${l}' `, 'ig'), '')
.replace(new RegExp(`\\${prefix}'${l}'`, 'ig'), '')
.replace(new RegExp(`\\${prefix}"${l}" `, 'ig'), '')
.replace(new RegExp(`\\${prefix}"${l}"`, 'ig'), '')
.replace(new RegExp(`\\${prefix}${l} `, 'ig'), '')
.replace(new RegExp(`\\${prefix}${l}`, 'ig'), '')
})
return text
}

View File

@ -5,7 +5,6 @@ import router from '@/router'
import TaskService from '@/services/task'
import TaskAssigneeService from '@/services/taskAssignee'
import LabelTaskService from '@/services/labelTask'
import UserService from '@/services/user'
import {playPop} from '@/helpers/playPop'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
@ -29,12 +28,21 @@ import {useProjectStore} from '@/stores/projects'
import {useAttachmentStore} from '@/stores/attachments'
import {useKanbanStore} from '@/stores/kanban'
import {useBaseStore} from '@/stores/base'
import ProjectUserService from '@/services/projectUsers'
interface MatchedAssignee extends IUser {
match: string,
}
// IDEA: maybe use a small fuzzy search here to prevent errors
function findPropertyByValue(object, key, value) {
return Object.values(object).find(
(l) => l[key]?.toLowerCase() === value.toLowerCase(),
)
function findPropertyByValue(object, key, value, fuzzy = false) {
return Object.values(object).find(l => {
if (fuzzy) {
return l[key]?.toLowerCase().includes(value.toLowerCase())
}
return l[key]?.toLowerCase() === value.toLowerCase()
})
}
// Check if the user exists in the search results
@ -42,9 +50,19 @@ function validateUser(
users: IUser[],
query: IUser['username'] | IUser['name'] | IUser['email'],
) {
return findPropertyByValue(users, 'username', query) ||
if (users.length === 1) {
return (
findPropertyByValue(users, 'username', query, true) ||
findPropertyByValue(users, 'name', query, true) ||
findPropertyByValue(users, 'email', query, true)
)
}
return (
findPropertyByValue(users, 'username', query) ||
findPropertyByValue(users, 'name', query) ||
findPropertyByValue(users, 'email', query)
)
}
// Check if the label exists
@ -63,14 +81,18 @@ async function addLabelToTask(task: ITask, label: ILabel) {
return response
}
async function findAssignees(parsedTaskAssignees: string[]): Promise<IUser[]> {
async function findAssignees(parsedTaskAssignees: string[], projectId: number): Promise<MatchedAssignee[]> {
if (parsedTaskAssignees.length <= 0) {
return []
}
const userService = new UserService()
const userService = new ProjectUserService()
const assignees = parsedTaskAssignees.map(async a => {
const users = await userService.getAll({}, {s: a})
const users = (await userService.getAll({projectId}, {s: a}))
.map(u => ({
...u,
match: a,
}))
return validateUser(users, a)
})
@ -388,15 +410,15 @@ export const useTaskStore = defineStore('task', () => {
cancel()
throw new Error('NO_PROJECT')
}
const assignees = await findAssignees(parsedTask.assignees)
const assignees = await findAssignees(parsedTask.assignees, foundProjectId)
// Only clean up those assignees from the task title which actually exist
let cleanedTitle = parsedTask.text
if (assignees.length > 0) {
const assigneePrefix = PREFIXES[quickAddMagicMode]?.assignee
if (assigneePrefix) {
cleanedTitle = cleanupItemText(cleanedTitle, assignees.map(a => a.username), assigneePrefix)
cleanedTitle = cleanupItemText(cleanedTitle, assignees.map(a => a.match), assigneePrefix)
}
}