Skip to content

Commit af8573e

Browse files
authored
fix: accept inbox thread URLs (#30)
1 parent 36ab27b commit af8573e

2 files changed

Lines changed: 105 additions & 101 deletions

File tree

src/lib/refs.test.ts

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -124,34 +124,49 @@ describe('parseCommsUrl', () => {
124124
expect(result).toEqual({ workspaceId: 12345, channelId: '7YpL3oZ4kZ9vP7Q1tR2sX3z' })
125125
})
126126

127-
it('parses short thread URL', () => {
128-
const result = parseCommsUrl('https://comms.todoist.com/12345/ch/CH1/t/TH1')
129-
expect(result).toEqual({ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1' })
130-
})
131-
132-
it('parses thread URL', () => {
133-
const result = parseCommsUrl('https://comms.todoist.com/a/12345/ch/CH1/t/TH1')
134-
expect(result).toEqual({ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1' })
135-
})
136-
137-
it('parses short thread with comment URL', () => {
138-
const result = parseCommsUrl('https://comms.todoist.com/12345/ch/CH1/t/TH1/c/CM1')
139-
expect(result).toEqual({
140-
workspaceId: 12345,
141-
channelId: 'CH1',
142-
threadId: 'TH1',
143-
commentId: 'CM1',
144-
})
145-
})
146-
147-
it('parses thread with comment URL', () => {
148-
const result = parseCommsUrl('https://comms.todoist.com/a/12345/ch/CH1/t/TH1/c/CM1')
149-
expect(result).toEqual({
150-
workspaceId: 12345,
151-
channelId: 'CH1',
152-
threadId: 'TH1',
153-
commentId: 'CM1',
154-
})
127+
it.each([
128+
[
129+
'short thread URL',
130+
'https://comms.todoist.com/12345/ch/CH1/t/TH1',
131+
{ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1' },
132+
],
133+
[
134+
'thread URL',
135+
'https://comms.todoist.com/a/12345/ch/CH1/t/TH1',
136+
{ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1' },
137+
],
138+
[
139+
'short thread with comment URL',
140+
'https://comms.todoist.com/12345/ch/CH1/t/TH1/c/CM1',
141+
{ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1', commentId: 'CM1' },
142+
],
143+
[
144+
'thread with comment URL',
145+
'https://comms.todoist.com/a/12345/ch/CH1/t/TH1/c/CM1',
146+
{ workspaceId: 12345, channelId: 'CH1', threadId: 'TH1', commentId: 'CM1' },
147+
],
148+
[
149+
'inbox thread URL',
150+
'https://comms.todoist.com/12345/inbox/t/TH1/',
151+
{ workspaceId: 12345, threadId: 'TH1' },
152+
],
153+
[
154+
'inbox thread with comment URL',
155+
'https://comms.todoist.com/12345/inbox/t/TH1/c/CM1',
156+
{ workspaceId: 12345, threadId: 'TH1', commentId: 'CM1' },
157+
],
158+
[
159+
'saved thread URL',
160+
'https://comms.todoist.com/12345/saved/t/TH1',
161+
{ workspaceId: 12345, threadId: 'TH1' },
162+
],
163+
[
164+
'people URL as workspace-only',
165+
'https://comms.todoist.com/12345/people/u/678',
166+
{ workspaceId: 12345 },
167+
],
168+
])('parses %s', (_description, url, expected) => {
169+
expect(parseCommsUrl(url)).toEqual(expected)
155170
})
156171

157172
it('parses conversation URL', () => {
@@ -270,12 +285,16 @@ describe('resolveThreadId', () => {
270285
expect(resolveThreadId('CbjxNkWHJBwcaVkoTCRgM')).toBe('CbjxNkWHJBwcaVkoTCRgM')
271286
})
272287

273-
it('resolves thread URLs', () => {
274-
expect(resolveThreadId('https://comms.todoist.com/a/12345/ch/CH1/t/TH1')).toBe('TH1')
275-
})
276-
277-
it('resolves thread URLs with comment suffix', () => {
278-
expect(resolveThreadId('https://comms.todoist.com/a/12345/ch/CH1/t/TH1/c/CM1')).toBe('TH1')
288+
it.each([
289+
['thread URL', 'https://comms.todoist.com/a/12345/ch/CH1/t/TH1'],
290+
['thread URL with comment suffix', 'https://comms.todoist.com/a/12345/ch/CH1/t/TH1/c/CM1'],
291+
['inbox thread URL', 'https://comms.todoist.com/12345/inbox/t/TH1/'],
292+
[
293+
'inbox thread URL with comment suffix',
294+
'https://comms.todoist.com/12345/inbox/t/TH1/c/CM1',
295+
],
296+
])('resolves %s', (_description, url) => {
297+
expect(resolveThreadId(url)).toBe('TH1')
279298
})
280299

281300
it('throws on invalid refs', () => {
@@ -526,55 +545,29 @@ describe('partitionNotifyIds', () => {
526545
})
527546

528547
describe('classifyCommsUrl', () => {
529-
it('classifies thread URL', () => {
530-
expect(classifyCommsUrl('https://comms.todoist.com/a/20/ch/CH1/t/TH1')).toEqual({
531-
entityType: 'thread',
532-
url: 'https://comms.todoist.com/a/20/ch/CH1/t/TH1',
533-
})
534-
})
535-
536-
it('classifies thread+comment URL as comment', () => {
537-
expect(classifyCommsUrl('https://comms.todoist.com/a/20/ch/CH1/t/TH1/c/CM1')).toEqual({
538-
entityType: 'comment',
539-
url: 'https://comms.todoist.com/a/20/ch/CH1/t/TH1/c/CM1',
540-
})
541-
})
542-
543-
it('classifies conversation URL', () => {
544-
expect(classifyCommsUrl('https://comms.todoist.com/a/20/msg/CV1')).toEqual({
545-
entityType: 'conversation',
546-
url: 'https://comms.todoist.com/a/20/msg/CV1',
547-
})
548-
})
549-
550-
it('classifies short conversation URL', () => {
551-
expect(classifyCommsUrl('https://comms.todoist.com/20/msg/CV1')).toEqual({
552-
entityType: 'conversation',
553-
url: 'https://comms.todoist.com/20/msg/CV1',
554-
})
555-
})
556-
557-
it('classifies message URL', () => {
558-
expect(classifyCommsUrl('https://comms.todoist.com/a/20/msg/CV1/m/MS1')).toEqual({
559-
entityType: 'message',
560-
url: 'https://comms.todoist.com/a/20/msg/CV1/m/MS1',
561-
})
562-
})
563-
564-
it('returns null for workspace-only URL', () => {
565-
expect(classifyCommsUrl('https://comms.todoist.com/a/20')).toBeNull()
566-
})
567-
568-
it('returns null for channel-only URL', () => {
569-
expect(classifyCommsUrl('https://comms.todoist.com/a/20/ch/CH1')).toBeNull()
570-
})
571-
572-
it('returns null for non-Comms URL', () => {
573-
expect(classifyCommsUrl('https://google.com/a/20/t/200')).toBeNull()
574-
})
575-
576-
it('returns null for invalid string', () => {
577-
expect(classifyCommsUrl('not-a-url')).toBeNull()
548+
it.each([
549+
['thread URL', 'https://comms.todoist.com/a/20/ch/CH1/t/TH1', 'thread'],
550+
['thread+comment URL', 'https://comms.todoist.com/a/20/ch/CH1/t/TH1/c/CM1', 'comment'],
551+
['inbox thread URL', 'https://comms.todoist.com/20/inbox/t/TH1/', 'thread'],
552+
['inbox thread+comment URL', 'https://comms.todoist.com/20/inbox/t/TH1/c/CM1', 'comment'],
553+
['conversation URL', 'https://comms.todoist.com/a/20/msg/CV1', 'conversation'],
554+
['short conversation URL', 'https://comms.todoist.com/20/msg/CV1', 'conversation'],
555+
['message URL', 'https://comms.todoist.com/a/20/msg/CV1/m/MS1', 'message'],
556+
] as const)('classifies %s', (_description, url, entityType) => {
557+
expect(classifyCommsUrl(url)).toEqual({ entityType, url })
558+
})
559+
560+
it.each([
561+
['inbox root URL', 'https://comms.todoist.com/20/inbox'],
562+
['inbox done URL', 'https://comms.todoist.com/20/inbox/done'],
563+
['inbox done thread-like URL', 'https://comms.todoist.com/20/inbox/done/t/TH1'],
564+
['workspace-only URL', 'https://comms.todoist.com/a/20'],
565+
['channel-only URL', 'https://comms.todoist.com/a/20/ch/CH1'],
566+
['malformed account URL', 'https://comms.todoist.com/a/ch/CH1/t/TH1'],
567+
['non-Comms URL', 'https://google.com/a/20/t/200'],
568+
['invalid string', 'not-a-url'],
569+
])('returns null for %s', (_description, url) => {
570+
expect(classifyCommsUrl(url)).toBeNull()
578571
})
579572
})
580573

src/lib/refs.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,27 +112,38 @@ export function parseCommsUrl(url: string): ParsedCommsUrl | null {
112112
routeStart = 1
113113
}
114114

115-
for (let index = routeStart; index < segments.length - 1; index += 2) {
116-
const value = segments[index + 1]
117-
switch (segments[index]) {
118-
case 'ch':
119-
result.channelId = value
120-
break
121-
case 't':
122-
result.threadId = value
123-
break
124-
case 'c':
125-
result.commentId = value
126-
break
127-
case 'msg':
128-
result.conversationId = value
129-
break
130-
case 'm':
131-
result.messageId = value
132-
break
115+
const parseRoutePairs = (start: number) => {
116+
for (let index = start; index < segments.length - 1; index += 2) {
117+
const value = segments[index + 1]
118+
switch (segments[index]) {
119+
case 'ch':
120+
result.channelId = value
121+
break
122+
case 't':
123+
result.threadId = value
124+
break
125+
case 'c':
126+
result.commentId = value
127+
break
128+
case 'msg':
129+
result.conversationId = value
130+
break
131+
case 'm':
132+
result.messageId = value
133+
break
134+
}
133135
}
134136
}
135137

138+
if (
139+
(segments[routeStart] === 'inbox' || segments[routeStart] === 'saved') &&
140+
segments[routeStart + 1] === 't'
141+
) {
142+
parseRoutePairs(routeStart + 1)
143+
} else if (segments[routeStart] !== 'inbox' && segments[routeStart] !== 'saved') {
144+
parseRoutePairs(routeStart)
145+
}
146+
136147
return Object.keys(result).length > 0 ? result : null
137148
} catch {
138149
return null

0 commit comments

Comments
 (0)