Skip to content

Commit 30942d2

Browse files
committed
improve
1 parent b946527 commit 30942d2

File tree

4 files changed

+81
-110
lines changed

4 files changed

+81
-110
lines changed

src/lib/components/AddTask.svelte

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,6 @@
6060
result.push({ text: value.slice(pos), type: 'text' })
6161
}
6262
63-
// Debug logging
64-
if (value.includes('#list 2 de')) {
65-
console.log('AddTask segments debug:', {
66-
value,
67-
matches,
68-
segments: result,
69-
totalLength: result.reduce((sum, s) => sum + s.text.length, 0),
70-
valueLength: value.length,
71-
})
72-
}
73-
7463
return result
7564
})
7665

src/lib/components/Tasks.svelte

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,44 +29,46 @@
2929
)
3030
3131
// Derived date match - parse from text AFTER stripping project
32+
// To avoid false matches across the project boundary (e.g., "dec #project 12" matching "dec 12"),
33+
// we parse the left and right halves separately when a project is present
3234
let parsedDate = $derived.by(() => {
33-
let textForDateParsing = newTaskContent
34-
35-
// If there's a project match, strip it first
36-
if (parsedProject?.match) {
37-
textForDateParsing = stripProjectMatch(
38-
newTaskContent,
39-
parsedProject.match
40-
)
35+
if (!parsedProject?.match) {
36+
// No project match, parse entire text
37+
return parseSmartDate(newTaskContent, {
38+
dateFormat: settings.dateFormat,
39+
})
4140
}
4241
43-
const dateResult = parseSmartDate(textForDateParsing, {
42+
// Split around project match and try each half
43+
const leftHalf = newTaskContent.slice(0, parsedProject.match.start).trimEnd()
44+
const rightHalf = newTaskContent.slice(parsedProject.match.end).trimStart()
45+
46+
// Try left half first
47+
const leftResult = parseSmartDate(leftHalf, {
4448
dateFormat: settings.dateFormat,
4549
})
50+
if (leftResult) {
51+
return leftResult // positions already correct for original text
52+
}
4653
47-
// If we stripped a project and found a date, find where it is in the original text
48-
if (dateResult?.match && parsedProject?.match) {
49-
const dateText = textForDateParsing.slice(
50-
dateResult.match.start,
51-
dateResult.match.end
52-
)
53-
54-
// Find this date text in the original, searching after the project match
55-
const searchStart = parsedProject.match.end
56-
const foundIndex = newTaskContent.indexOf(dateText, searchStart)
57-
58-
if (foundIndex !== -1) {
59-
dateResult.match = {
60-
start: foundIndex,
61-
end: foundIndex + dateText.length,
62-
}
63-
} else {
64-
// Couldn't find date in original text, don't highlight it
65-
return null
54+
// Try right half
55+
const rightResult = parseSmartDate(rightHalf, {
56+
dateFormat: settings.dateFormat,
57+
})
58+
if (rightResult) {
59+
// Offset positions to account for stripped whitespace and project
60+
const rightStartOffset = parsedProject.match.end +
61+
(newTaskContent.slice(parsedProject.match.end).length - rightHalf.length)
62+
return {
63+
...rightResult,
64+
match: {
65+
start: rightResult.match.start + rightStartOffset,
66+
end: rightResult.match.end + rightStartOffset,
67+
},
6668
}
6769
}
6870
69-
return dateResult
71+
return null
7072
})
7173
7274
function handleVisibilityChange() {
@@ -185,20 +187,20 @@
185187
let due = null
186188
let projectId = null
187189
188-
// Strip date match
189-
if (parsedDate?.match) {
190-
const cleaned = stripDateMatch(content, parsedDate.match)
191-
content = cleaned || content
192-
due = formatTaskDue(parsedDate.date, parsedDate.hasTime)
193-
}
194-
195-
// Strip project match
190+
// Strip project match first (matches parsing precedence)
196191
if (parsedProject?.match) {
197192
const cleaned = stripProjectMatch(content, parsedProject.match)
198193
content = cleaned || content
199194
projectId = parsedProject.projectId
200195
}
201196
197+
// Then strip date match
198+
if (parsedDate?.match) {
199+
const cleaned = stripDateMatch(content, parsedDate.match)
200+
content = cleaned || content
201+
due = formatTaskDue(parsedDate.date, parsedDate.hasTime)
202+
}
203+
202204
try {
203205
addingTask = true
204206
await api.addTask(content, due, projectId)

src/lib/date-matcher.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,13 @@ export function parseSmartDate(input, maybeNow, maybeOptions) {
505505
}
506506
}
507507

508-
export function stripDateMatch(text, match) {
508+
/**
509+
* Strip any match from text (generic utility for both date and project matches)
510+
* @param {string} text - The original text
511+
* @param {Object} match - Match object with {start, end}
512+
* @returns {string} Text with match removed
513+
*/
514+
export function stripMatch(text, match) {
509515
if (!match) return text.trim()
510516
const before = text.slice(0, match.start).trimEnd()
511517
const after = text.slice(match.end).trimStart()
@@ -514,6 +520,9 @@ export function stripDateMatch(text, match) {
514520
return `${before} ${after}`
515521
}
516522

523+
// Backwards compatibility
524+
export const stripDateMatch = stripMatch
525+
517526
export function formatTaskDue(date, hasTime) {
518527
if (!date) return null
519528
const pad = (n) => String(n).padStart(2, '0')

src/lib/project-matcher.js

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Project/List matcher for task input
22
// Returns matched span + project info for highlighting and assignment.
33

4+
import { stripMatch } from './date-matcher.js'
5+
46
/**
57
* Parse project match from input text
6-
* Supports #project and #"project name" syntax
8+
* Supports #project name and #"project name" syntax
79
*
810
* @param {string} input - The task input text
911
* @param {Array<{id: string, name: string}>} projects - Available projects/lists
@@ -12,32 +14,40 @@
1214
export function parseProjectMatch(input, projects) {
1315
if (!input || !projects || projects.length === 0) return null
1416

15-
const text = input
16-
const lower = input.toLowerCase()
17-
1817
// Find all # symbols
1918
let index = 0
20-
while (index < text.length) {
21-
index = text.indexOf('#', index)
19+
while (index < input.length) {
20+
index = input.indexOf('#', index)
2221
if (index === -1) break
2322

2423
// Check if this is the start of a project reference
2524
// Must be at start or preceded by whitespace
26-
if (index > 0 && !/\s/.test(text[index - 1])) {
25+
if (index > 0 && !/\s/.test(input[index - 1])) {
2726
index++
2827
continue
2928
}
3029

31-
let matchEnd = -1
32-
let matchText = ''
33-
3430
// Check for quoted syntax: #"project name"
35-
if (text[index + 1] === '"') {
31+
if (input[index + 1] === '"') {
3632
const quoteStart = index + 2
37-
const quoteEnd = text.indexOf('"', quoteStart)
33+
const quoteEnd = input.indexOf('"', quoteStart)
3834
if (quoteEnd !== -1) {
39-
matchText = text.slice(quoteStart, quoteEnd)
40-
matchEnd = quoteEnd + 1
35+
const matchText = input.slice(quoteStart, quoteEnd)
36+
const matchLower = matchText.toLowerCase()
37+
const project = projects.find(
38+
(p) => p.name.toLowerCase() === matchLower
39+
)
40+
41+
if (project) {
42+
return {
43+
match: {
44+
start: index,
45+
end: quoteEnd + 1,
46+
},
47+
projectId: project.id,
48+
projectName: project.name,
49+
}
50+
}
4151
}
4252
} else {
4353
// Unquoted syntax with greedy matching: #project or #project name
@@ -47,22 +57,22 @@ export function parseProjectMatch(input, projects) {
4757
let pos = index + 1
4858

4959
// Extract words and try matching after each word
50-
while (pos < text.length && text[pos] !== '#') {
60+
while (pos < input.length && input[pos] !== '#') {
5161
// Skip whitespace
52-
while (pos < text.length && /\s/.test(text[pos])) {
62+
while (pos < input.length && /\s/.test(input[pos])) {
5363
pos++
5464
}
5565

56-
if (pos >= text.length || text[pos] === '#') break
66+
if (pos >= input.length || input[pos] === '#') break
5767

5868
// Get next word
5969
let wordEnd = pos
60-
while (wordEnd < text.length && !/[\s#]/.test(text[wordEnd])) {
70+
while (wordEnd < input.length && !/[\s#]/.test(input[wordEnd])) {
6171
wordEnd++
6272
}
6373

6474
if (wordEnd > pos) {
65-
words.push(text.slice(pos, wordEnd))
75+
words.push(input.slice(pos, wordEnd))
6676

6777
// Try matching accumulated words
6878
const candidate = words.join(' ')
@@ -84,48 +94,14 @@ export function parseProjectMatch(input, projects) {
8494
}
8595

8696
if (longestMatch) {
87-
// Found a match with greedy matching, return immediately
88-
const result = {
97+
return {
8998
match: {
9099
start: index,
91100
end: longestMatch.end,
92101
},
93102
projectId: longestMatch.project.id,
94103
projectName: longestMatch.project.name,
95104
}
96-
97-
// Debug logging
98-
if (input.includes('#list 2 de')) {
99-
console.log('Project matcher debug:', {
100-
input,
101-
matchedText: input.slice(index, longestMatch.end),
102-
start: index,
103-
end: longestMatch.end,
104-
projectName: longestMatch.project.name,
105-
})
106-
}
107-
108-
return result
109-
}
110-
}
111-
112-
// For quoted syntax, we still need to match against available projects
113-
if (matchText && matchEnd !== -1) {
114-
// Try to match against available projects (case-insensitive)
115-
const matchLower = matchText.toLowerCase()
116-
const project = projects.find(
117-
(p) => p.name.toLowerCase() === matchLower
118-
)
119-
120-
if (project) {
121-
return {
122-
match: {
123-
start: index,
124-
end: matchEnd,
125-
},
126-
projectId: project.id,
127-
projectName: project.name,
128-
}
129105
}
130106
}
131107

@@ -136,16 +112,11 @@ export function parseProjectMatch(input, projects) {
136112
}
137113

138114
/**
139-
* Strip project match from text
115+
* Strip project match from text (uses shared stripMatch utility)
140116
* @param {string} text - The original text
141117
* @param {Object} match - Match object with {start, end}
142118
* @returns {string} Text with project match removed
143119
*/
144120
export function stripProjectMatch(text, match) {
145-
if (!match) return text.trim()
146-
const before = text.slice(0, match.start).trimEnd()
147-
const after = text.slice(match.end).trimStart()
148-
if (!before) return after
149-
if (!after) return before
150-
return `${before} ${after}`
121+
return stripMatch(text, match)
151122
}

0 commit comments

Comments
 (0)