fix(quick add magic): time parsing for certain conditions (#2367)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2367 Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
2df2bd38e2
commit
b24d5f2dce
3 changed files with 64 additions and 22 deletions
|
@ -11,7 +11,7 @@
|
|||
"build:dev": "vite build -m development --outDir dist-dev/",
|
||||
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
|
||||
"cypress:open": "cypress open",
|
||||
"test:unit": "vitest",
|
||||
"test:unit": "vitest --run",
|
||||
"test:unit-watch": "vitest watch",
|
||||
"test:frontend": "cypress run",
|
||||
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||
|
|
|
@ -12,7 +12,9 @@ interface dateFoundResult {
|
|||
date: Date | null,
|
||||
}
|
||||
|
||||
export const parseDate = (text: string): dateParseResult => {
|
||||
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
|
||||
|
||||
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
|
||||
const lowerText: string = text.toLowerCase()
|
||||
|
||||
if (lowerText.includes('today')) {
|
||||
|
@ -62,39 +64,44 @@ export const parseDate = (text: string): dateParseResult => {
|
|||
}
|
||||
|
||||
parsed = getDayFromText(text)
|
||||
if (parsed.date !== null) {
|
||||
const month = getMonthFromText(text, parsed.date)
|
||||
return addTimeToDate(text, month.date, parsed.foundText)
|
||||
}
|
||||
|
||||
parsed = getDateFromTextIn(text, now)
|
||||
if (parsed.date !== null) {
|
||||
return addTimeToDate(text, parsed.date, parsed.foundText)
|
||||
}
|
||||
|
||||
parsed = getDateFromTextIn(text)
|
||||
if (parsed.date !== null) {
|
||||
parsed = getDateFromText(text)
|
||||
|
||||
if (parsed.date === null) {
|
||||
return {
|
||||
newText: replaceAll(text, parsed.foundText, ''),
|
||||
date: parsed.date,
|
||||
}
|
||||
}
|
||||
|
||||
parsed = getDateFromText(text)
|
||||
|
||||
return {
|
||||
newText: replaceAll(text, parsed.foundText, ''),
|
||||
date: parsed.date,
|
||||
}
|
||||
return addTimeToDate(text, parsed.date, parsed.foundText)
|
||||
}
|
||||
|
||||
const addTimeToDate = (text: string, date: Date, match: string | null): dateParseResult => {
|
||||
if (match === null) {
|
||||
const addTimeToDate = (text: string, date: Date, previousMatch: string | null): dateParseResult => {
|
||||
previousMatch = previousMatch?.trim() || ''
|
||||
text = replaceAll(text, previousMatch, '')
|
||||
if (previousMatch === null) {
|
||||
return {
|
||||
newText: text,
|
||||
date: null,
|
||||
}
|
||||
}
|
||||
|
||||
const matcher = new RegExp(`(${match} (at|@) )([0-9][0-9]?(:[0-9][0-9]?)?( ?(a|p)m)?)`, 'ig')
|
||||
const timeRegex = ' (at|@) ([0-9][0-9]?(:[0-9][0-9]?)?( ?(a|p)m)?)'
|
||||
const matcher = new RegExp(timeRegex, 'ig')
|
||||
const results = matcher.exec(text)
|
||||
|
||||
if (results !== null) {
|
||||
const time = results[3]
|
||||
const time = results[2]
|
||||
const parts = time.split(':')
|
||||
let hours = parseInt(parts[0])
|
||||
let minutes = 0
|
||||
|
@ -110,7 +117,7 @@ const addTimeToDate = (text: string, date: Date, match: string | null): datePars
|
|||
date.setSeconds(0)
|
||||
}
|
||||
|
||||
const replace = results !== null ? results[0] : match
|
||||
const replace = results !== null ? results[0] : previousMatch
|
||||
return {
|
||||
newText: replaceAll(text, replace, ''),
|
||||
date: date,
|
||||
|
@ -127,10 +134,10 @@ export const getDateFromText = (text: string, now: Date = new Date()) => {
|
|||
let containsYear: boolean = true
|
||||
if (result === null) {
|
||||
// 2. Try parsing the date as something like "jan 21" or "21 jan"
|
||||
const monthRegex: RegExp = / ((jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) [0-9][0-9]?|[0-9][0-9]? (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec))/ig
|
||||
const monthRegex: RegExp = new RegExp(` (${monthsRegexGroup} [0-9][0-9]?|[0-9][0-9]? ${monthsRegexGroup})`, 'ig')
|
||||
results = monthRegex.exec(text)
|
||||
result = results === null ? null : `${results[0]} ${now.getFullYear()}`
|
||||
foundText = results === null ? '' : results[0]
|
||||
result = results === null ? null : `${results[0]} ${now.getFullYear()}`.trim()
|
||||
foundText = results === null ? '' : results[0].trim()
|
||||
containsYear = false
|
||||
|
||||
if (result === null) {
|
||||
|
@ -309,7 +316,7 @@ const getDayFromText = (text: string) => {
|
|||
while (date < now) {
|
||||
date.setMonth(date.getMonth() + 1)
|
||||
}
|
||||
|
||||
|
||||
if (date.getDate() !== day) {
|
||||
date.setDate(day)
|
||||
}
|
||||
|
@ -320,6 +327,25 @@ const getDayFromText = (text: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getMonthFromText = (text: string, date: Date) => {
|
||||
const matcher = new RegExp(monthsRegexGroup, 'ig')
|
||||
const results = matcher.exec(text)
|
||||
|
||||
if (results === null) {
|
||||
return {
|
||||
newText: text,
|
||||
date,
|
||||
}
|
||||
}
|
||||
|
||||
const fullDate = new Date(`${results[0]} 1 ${(new Date()).getFullYear()}`)
|
||||
date.setMonth(fullDate.getMonth())
|
||||
return {
|
||||
newText: replaceAll(text, results[0], ''),
|
||||
date,
|
||||
}
|
||||
}
|
||||
|
||||
const getDateFromInterval = (interval: number): Date => {
|
||||
const newDate = new Date()
|
||||
newDate.setDate(newDate.getDate() + interval)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
|
||||
|
||||
import {parseTaskText, PrefixMode} from './parseTaskText'
|
||||
import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate'
|
||||
import {getDateFromText, getDateFromTextIn, parseDate} from '../helpers/time/parseDate'
|
||||
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
|
||||
|
@ -359,7 +359,7 @@ describe('Parse Task Text', () => {
|
|||
it('should not recognize dates in urls', () => {
|
||||
const text = 'https://some-url.org/blog/2019/1/233526-some-more-text'
|
||||
const result = parseTaskText(text)
|
||||
|
||||
|
||||
expect(result.text).toBe(text)
|
||||
expect(result.date).toBeNull()
|
||||
})
|
||||
|
@ -483,6 +483,15 @@ describe('Parse Task Text', () => {
|
|||
now.setMinutes(0)
|
||||
now.setSeconds(0)
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(now)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
const cases = {
|
||||
'Lorem Ipsum in 1 hour': '2021-6-24 13:0',
|
||||
'in 2 hours': '2021-6-24 14:0',
|
||||
|
@ -493,11 +502,18 @@ describe('Parse Task Text', () => {
|
|||
'in 4 weeks': '2021-7-22 12:0',
|
||||
'in 1 month': '2021-7-24 12:0',
|
||||
'in 3 months': '2021-9-24 12:0',
|
||||
'Something in 5 days at 10:00': '2021-6-29 10:0',
|
||||
'Something 17th at 10:00': '2021-7-17 10:0',
|
||||
'Something sep 17 at 10:00': '2021-9-17 10:0',
|
||||
'Something sep 17th at 10:00': '2021-9-17 10:0',
|
||||
'Something at 10:00 in 5 days': '2021-6-29 10:0',
|
||||
'Something at 10:00 17th': '2021-7-17 10:0',
|
||||
'Something at 10:00 sep 17th': '2021-9-17 10:0',
|
||||
}
|
||||
|
||||
for (const c in cases) {
|
||||
it(`should parse '${c}' as '${cases[c]}'`, () => {
|
||||
const {date} = getDateFromTextIn(c, now)
|
||||
const {date} = parseDate(c, now)
|
||||
if (date === null && cases[c] === null) {
|
||||
expect(date).toBeNull()
|
||||
return
|
||||
|
|
Loading…
Reference in a new issue