From 8419794b6527aca4570b86a25c618d5b40568e21 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 23 Jan 2026 23:23:42 +0100 Subject: [PATCH] fix(quick-add-magic): prevent parsing partial keywords in words (#2140) - Fixed date parser incorrectly extracting date components from within words - "Renovation - 2nd Floor Bath" no longer becomes "Reation - Floor Bath" with a due date of November 2nd ## Changes - `getMonthFromText` now requires word boundaries, preventing "nov" from matching inside "Renovation" or "mar" inside "Remark" - `getDayFromText` now only matches ordinals when followed by end-of-string, time expressions, or month names, preventing "2nd Floor" from being parsed as a date Resolves https://community.vikunja.io/t/quick-add-magic-unintended-date-parsing/4259 --- frontend/src/helpers/time/parseDate.ts | 6 ++++-- frontend/src/modules/parseTaskText.test.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/helpers/time/parseDate.ts b/frontend/src/helpers/time/parseDate.ts index cf2d37f8e..95bd852de 100644 --- a/frontend/src/helpers/time/parseDate.ts +++ b/frontend/src/helpers/time/parseDate.ts @@ -316,7 +316,9 @@ const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundRes } const getDayFromText = (text: string, now: Date = new Date()) => { - const matcher = /(^| )(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\.)($| )/ig + // Only match ordinals when followed by end-of-string, time expressions, or month names + // This prevents matching "2nd Floor" or "13th floor" as dates + const matcher = new RegExp('(^| )(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\\.)(?=$| at | @ | ' + monthsRegexGroup + ')', 'ig') const results = matcher.exec(text) if (results === null) { return { @@ -348,7 +350,7 @@ const getDayFromText = (text: string, now: Date = new Date()) => { } const getMonthFromText = (text: string, date: Date) => { - const matcher = new RegExp(monthsRegexGroup, 'ig') + const matcher = new RegExp('\\b' + monthsRegexGroup + '\\b', 'ig') const results = matcher.exec(text) if (results === null) { diff --git a/frontend/src/modules/parseTaskText.test.ts b/frontend/src/modules/parseTaskText.test.ts index f4cec87d9..621fbe66f 100644 --- a/frontend/src/modules/parseTaskText.test.ts +++ b/frontend/src/modules/parseTaskText.test.ts @@ -315,7 +315,7 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum github') expect(result.date).toBeNull() }) - describe('Should not recognize weekdays in words', () => { + describe('Should not recognize partial keywords in words', () => { const cases = [ 'renewed', 'github', @@ -328,6 +328,11 @@ describe('Parse Task Text', () => { 'monitor blood pressure', 'Monitor blood pressure', 'buy almonds', + 'Renovation', + 'Remark', + 'Renovation - 2nd Floor Bath', + 'Remark - 13th floor', + '13th floor - remark', ] cases.forEach(c => { @@ -349,6 +354,12 @@ describe('Parse Task Text', () => { expect(result.text).toBe(`Lorem Ipsum ${c} dolor`) expect(result.date).toBeNull() }) + it(`should not recognize text with ${c} as special keywords`, () => { + const result = parseTaskText(c) + + expect(result.text).toBe(c) + expect(result.date).toBeNull() + }) }) }) it('should not recognize date number with no spacing around them', () => {