Skip to content

Commit b946527

Browse files
committed
add project matching
1 parent 2302718 commit b946527

File tree

5 files changed

+314
-24
lines changed

5 files changed

+314
-24
lines changed

src/lib/backends/google-tasks-backend.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ class GoogleTasksBackendExtension extends TaskBackend {
159159
)
160160
}
161161

162+
// Handle empty responses (e.g., DELETE requests)
163+
if (retryResponse.status === 204 || retryResponse.headers.get('content-length') === '0') {
164+
return null
165+
}
166+
162167
return retryResponse.json()
163168
} catch (error) {
164169
// Token refresh failed, user needs to sign in again
@@ -174,6 +179,11 @@ class GoogleTasksBackendExtension extends TaskBackend {
174179
)
175180
}
176181

182+
// Handle empty responses (e.g., DELETE requests)
183+
if (response.status === 204 || response.headers.get('content-length') === '0') {
184+
return null
185+
}
186+
177187
return response.json()
178188
}
179189

@@ -341,16 +351,18 @@ class GoogleTasksBackendExtension extends TaskBackend {
341351
* Add a new task
342352
* Note: Google Tasks API only supports dates, not times
343353
*/
344-
async addTask(content, due) {
354+
async addTask(content, due, tasklistId) {
345355
const taskData = { title: content }
346356
if (due) {
347357
// Google Tasks only supports date (YYYY-MM-DD), strip time if present
348358
const dateOnly = due.split('T')[0]
349359
taskData.due = dateOnly + 'T00:00:00.000Z'
350360
}
351361

362+
const targetListId = tasklistId || this.defaultTasklistId
363+
352364
const result = await this.apiRequest(
353-
`/lists/${this.defaultTasklistId}/tasks`,
365+
`/lists/${targetListId}/tasks`,
354366
{
355367
method: 'POST',
356368
body: JSON.stringify(taskData),

src/lib/backends/todoist-backend.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ class TodoistBackend extends TaskBackend {
267267
/**
268268
* Add a new task
269269
*/
270-
async addTask(content, due) {
270+
async addTask(content, due, projectId) {
271271
const tempId = crypto.randomUUID()
272272
const commands = [
273273
{
@@ -283,6 +283,7 @@ class TodoistBackend extends TaskBackend {
283283
},
284284
}
285285
: {}),
286+
...(projectId ? { project_id: projectId } : {}),
286287
},
287288
},
288289
]

src/lib/components/AddTask.svelte

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
let {
33
value = $bindable(''),
44
parsed = $bindable(null),
5+
parsedProject = null,
56
placeholder = 'new task',
67
disabled = false,
78
loading = false,
@@ -19,12 +20,60 @@
1920
oninput?.(event.target.value)
2021
}
2122
22-
const match = $derived(parsed?.match)
23-
const before = $derived(match ? value.slice(0, match.start) : value)
24-
const matchedText = $derived(
25-
match ? value.slice(match.start, match.end) : ''
26-
)
27-
const after = $derived(match ? value.slice(match.end) : '')
23+
// Build text segments with multiple highlights
24+
const segments = $derived.by(() => {
25+
if (!value) return []
26+
27+
const matches = []
28+
if (parsed?.match) {
29+
matches.push({ ...parsed.match, type: 'date' })
30+
}
31+
if (parsedProject?.match) {
32+
matches.push({ ...parsedProject.match, type: 'project' })
33+
}
34+
35+
if (matches.length === 0) {
36+
return [{ text: value, type: 'text' }]
37+
}
38+
39+
// Sort matches by start position
40+
matches.sort((a, b) => a.start - b.start)
41+
42+
const result = []
43+
let pos = 0
44+
45+
for (const match of matches) {
46+
// Add text before this match
47+
if (pos < match.start) {
48+
result.push({ text: value.slice(pos, match.start), type: 'text' })
49+
}
50+
// Add the match
51+
result.push({
52+
text: value.slice(match.start, match.end),
53+
type: match.type,
54+
})
55+
pos = match.end
56+
}
57+
58+
// Add remaining text
59+
if (pos < value.length) {
60+
result.push({ text: value.slice(pos), type: 'text' })
61+
}
62+
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+
74+
return result
75+
})
76+
2877
const showPlaceholder = $derived(!value)
2978
</script>
3079
@@ -34,12 +83,16 @@
3483
<div class="input-overlay" aria-hidden="true">
3584
{#if showPlaceholder}
3685
<span class="placeholder">{placeholder}</span>
37-
{:else if match}
38-
<span>{before}</span><span class="date-highlight"
39-
>{matchedText}</span
40-
><span>{after}</span>
4186
{:else}
42-
<span>{value}</span>
87+
{#each segments as segment}
88+
{#if segment.type === 'date'}
89+
<span class="date-highlight">{segment.text}</span>
90+
{:else if segment.type === 'project'}
91+
<span class="project-highlight">{segment.text}</span>
92+
{:else}
93+
<span>{segment.text}</span>
94+
{/if}
95+
{/each}
4396
{/if}
4497
</div>
4598
<input
@@ -91,6 +144,9 @@
91144
.date-highlight {
92145
color: var(--txt-1);
93146
}
147+
.project-highlight {
148+
color: var(--txt-1);
149+
}
94150
.add-task-input {
95151
flex: 1;
96152
background: transparent;

src/lib/components/Tasks.svelte

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,66 @@
99
stripDateMatch,
1010
formatTaskDue,
1111
} from '../date-matcher.js'
12+
import { parseProjectMatch, stripProjectMatch } from '../project-matcher.js'
1213
1314
let api = null
1415
let tasks = $state([])
16+
let availableProjects = $state([])
1517
let syncing = $state(true)
1618
let error = $state('')
1719
let initialLoad = $state(true)
1820
let previousToken = $state(null)
1921
let taskCount = $derived(tasks.filter((task) => !task.checked).length)
2022
let newTaskContent = $state('')
2123
let addingTask = $state(false)
22-
let parsedDate = $state(null)
2324
let togglingTasks = $state(new Set())
2425
26+
// Derived project match
27+
let parsedProject = $derived(
28+
parseProjectMatch(newTaskContent, availableProjects)
29+
)
30+
31+
// Derived date match - parse from text AFTER stripping project
32+
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+
)
41+
}
42+
43+
const dateResult = parseSmartDate(textForDateParsing, {
44+
dateFormat: settings.dateFormat,
45+
})
46+
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
66+
}
67+
}
68+
69+
return dateResult
70+
})
71+
2572
function handleVisibilityChange() {
2673
if (document.visibilityState === 'visible' && api) {
2774
loadTasks()
@@ -45,16 +92,11 @@
4592
initializeAPI(backend, token, tokenChanged)
4693
})
4794
48-
$effect(() => {
49-
parsedDate = parseSmartDate(newTaskContent, {
50-
dateFormat: settings.dateFormat,
51-
})
52-
})
53-
5495
async function initializeAPI(backend, token, clearLocalData = false) {
5596
if (backend === 'todoist' && !token) {
5697
api = null
5798
tasks = []
99+
availableProjects = []
58100
syncing = false
59101
error = 'no todoist api token'
60102
return
@@ -63,6 +105,7 @@
63105
if (backend === 'google-tasks' && !isChrome()) {
64106
api = null
65107
tasks = []
108+
availableProjects = []
66109
syncing = false
67110
error = 'google tasks only works in chrome'
68111
return
@@ -71,6 +114,7 @@
71114
if (backend === 'google-tasks' && !settings.googleTasksSignedIn) {
72115
api = null
73116
tasks = []
117+
availableProjects = []
74118
syncing = false
75119
error = 'not signed in to google'
76120
return
@@ -86,6 +130,7 @@
86130
if (clearLocalData) {
87131
api.clearLocalData()
88132
tasks = []
133+
availableProjects = []
89134
}
90135
await loadTasks(true)
91136
} catch (err) {
@@ -101,6 +146,19 @@
101146
error = ''
102147
await api.sync()
103148
tasks = api.getTasks()
149+
150+
// Update available projects/lists
151+
if (settings.taskBackend === 'todoist') {
152+
availableProjects = (api.data?.projects || []).map((p) => ({
153+
id: p.id,
154+
name: p.name,
155+
}))
156+
} else if (settings.taskBackend === 'google-tasks') {
157+
availableProjects = (api.data?.tasklists || []).map((tl) => ({
158+
id: tl.id,
159+
name: tl.title,
160+
}))
161+
}
104162
} catch (err) {
105163
// Check if this is an auth error for Google Tasks
106164
if (
@@ -125,14 +183,25 @@
125183
126184
let content = raw
127185
let due = null
186+
let projectId = null
187+
188+
// Strip date match
128189
if (parsedDate?.match) {
129-
const cleaned = stripDateMatch(raw, parsedDate.match)
130-
content = cleaned || raw
190+
const cleaned = stripDateMatch(content, parsedDate.match)
191+
content = cleaned || content
131192
due = formatTaskDue(parsedDate.date, parsedDate.hasTime)
132193
}
194+
195+
// Strip project match
196+
if (parsedProject?.match) {
197+
const cleaned = stripProjectMatch(content, parsedProject.match)
198+
content = cleaned || content
199+
projectId = parsedProject.projectId
200+
}
201+
133202
try {
134203
addingTask = true
135-
await api.addTask(content, due)
204+
await api.addTask(content, due, projectId)
136205
newTaskContent = ''
137206
await loadTasks()
138207
} catch (err) {
@@ -304,6 +373,7 @@
304373
<AddTask
305374
bind:value={newTaskContent}
306375
bind:parsed={parsedDate}
376+
{parsedProject}
307377
disabled={addingTask}
308378
loading={addingTask}
309379
show={tasks.length === 0}

0 commit comments

Comments
 (0)