Skip to content

Commit af0b35e

Browse files
authored
feat: add link support for discussion (#146)
Co-authored-by: Araxeus
1 parent 9476a27 commit af0b35e

File tree

4 files changed

+175
-9
lines changed

4 files changed

+175
-9
lines changed

src/lib/components/dashboard/notifications/NotificationDescription.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
width: 100%;
7878
-webkit-box-orient: vertical;
7979
color: variables.$grey-4;
80+
hyphens: auto;
8081
-webkit-line-clamp: 2;
8182
word-wrap: break-word;
8283

src/lib/helpers/createNotificationData.ts

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { page } from '$app/stores';
2+
import { getDiscussionUrl } from '$lib/helpers/searchNotificationHelper';
23
import {
34
ClosedIssueIcon,
45
CommitIcon,
@@ -43,10 +44,11 @@ type PullRequestEvent = {
4344
type FetchOptions = Parameters<typeof fetchGithub>[1];
4445

4546
export async function createNotificationData(
46-
{ id, repository, subject, unread: isUnread, updated_at, reason }: GithubNotification,
47+
githubNotification: GithubNotification,
4748
savedNotifications: SavedNotifications,
4849
firstTime: boolean
4950
): Promise<NotificationData | null> {
51+
const { id, repository, subject, unread: isUnread, updated_at, reason } = githubNotification;
5052
const previous = Array.isArray(savedNotifications)
5153
? savedNotifications.find((n) => n.id === id)
5254
: undefined;
@@ -267,14 +269,32 @@ export async function createNotificationData(
267269
break;
268270
}
269271

270-
case 'Discussion':
272+
case 'Discussion': {
273+
const data = await getDiscussionUrl(githubNotification).then(({ url, latestCommentEdge }) => {
274+
if (!latestCommentEdge) {
275+
return {
276+
description: 'New activity on discussion'
277+
};
278+
}
279+
url += '#discussioncomment-' + latestCommentEdge.node.databaseId;
280+
const author = latestCommentEdge.node.author;
281+
return {
282+
author: {
283+
login: author.login,
284+
avatar: author.avatarUrl,
285+
bot: author.__typename === 'Bot'
286+
},
287+
description: commentBodyToDescription(latestCommentEdge.node.bodyText),
288+
url
289+
};
290+
});
271291
value = {
272292
...common,
273-
description: 'New activity on discussion',
293+
...data,
274294
icon: DiscussionIcon
275295
};
276296
break;
277-
297+
}
278298
case 'CheckSuite': {
279299
const splited = subject.title.split(' ');
280300
const workflowName = splited[0];
@@ -349,6 +369,10 @@ export async function createNotificationData(
349369
};
350370
}
351371

372+
const commentBodyToDescription = (body: string) => {
373+
return `*commented*: _${body.slice(0, 100)}${body.length > 100 ? '...' : ''}_`;
374+
};
375+
352376
async function getLatestComment(
353377
url: string,
354378
fetchOptions: FetchOptions
@@ -361,8 +385,8 @@ async function getLatestComment(
361385
avatar: comment.user.avatar_url,
362386
bot: comment.user.type === 'Bot'
363387
};
364-
const body = removeMarkdownSymbols(comment.body).slice(0, 100);
365-
const description = `*commented*: _${body}${body.length < 100 ? '...' : ''}_`;
388+
const body = removeMarkdownSymbols(comment.body);
389+
const description = commentBodyToDescription(body);
366390
return { author, description, time: comment.created_at, url: comment.html_url };
367391
}
368392

src/lib/helpers/fetchGithub.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ export async function fetchGithub<T = void>(url: string, options?: Options): Pro
2626
cache: options?.noCache ? 'no-store' : undefined
2727
});
2828

29-
if (options?.method) return undefined as T;
29+
if (options?.method === 'PATCH') return undefined as T;
3030

3131
if (response.ok) {
32-
const data = await response.json();
33-
return data;
32+
return await response.json();
3433
}
3534

3635
throw new Error(`${response.status}`);
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// source code from https://github.com/gitify-app/gitify/pull/538
2+
import { fetchGithub } from '$lib/helpers/fetchGithub';
3+
import type { GithubNotification } from '$lib/types';
4+
5+
export type ViewerSubscription = 'IGNORED' | 'SUBSCRIBED' | 'UNSUBSCRIBED';
6+
7+
export interface GraphQLSearch {
8+
data: {
9+
search: {
10+
edges: DiscussionEdge[];
11+
};
12+
};
13+
}
14+
15+
export interface DiscussionEdge {
16+
node: {
17+
viewerSubscription: ViewerSubscription;
18+
title: string;
19+
url: string;
20+
comments: {
21+
edges: DiscussionCommentEdge[];
22+
};
23+
};
24+
}
25+
26+
// https://docs.github.com/en/graphql/reference/interfaces#actor
27+
export interface Actor {
28+
login: string;
29+
avatarUrl: string;
30+
__typename: 'Bot' | 'EnterpriseUserAccount' | 'Mannequin' | 'Organization' | 'User';
31+
}
32+
33+
export interface DiscussionCommentEdge {
34+
node: {
35+
databaseId: string | number;
36+
createdAt: string;
37+
author: Actor;
38+
bodyText: string;
39+
replies: {
40+
edges: DiscussionSubCommentEdge[];
41+
};
42+
};
43+
}
44+
45+
export interface DiscussionSubCommentEdge {
46+
node: {
47+
databaseId: string | number;
48+
createdAt: string;
49+
author: Actor;
50+
bodyText: string;
51+
};
52+
}
53+
54+
const addHours = (date: string, hours: number) =>
55+
new Date(new Date(date).getTime() + hours * 36e5).toISOString();
56+
57+
const queryString = (repo: string, title: string, lastUpdated: string) =>
58+
`${title} in:title repo:${repo} updated:>${addHours(lastUpdated, -2)}`;
59+
60+
export const getLatestDiscussionCommentEdge = (comments: DiscussionCommentEdge[]) =>
61+
comments
62+
.flatMap((comment) => comment.node.replies.edges)
63+
.concat([comments.at(-1) || ({} as DiscussionCommentEdge)])
64+
.reduce((a, b) => (a.node.createdAt > b.node.createdAt ? a : b));
65+
66+
export async function getDiscussionUrl(notification: GithubNotification): Promise<{
67+
url: string;
68+
latestCommentEdge: DiscussionSubCommentEdge | undefined;
69+
}> {
70+
const response: GraphQLSearch = await fetchGithub('graphql', {
71+
method: 'POST',
72+
body: {
73+
query: `{
74+
search(query:"${queryString(
75+
notification.repository.full_name,
76+
notification.subject.title,
77+
notification.updated_at
78+
)}"
79+
type: DISCUSSION
80+
first: 10
81+
) {
82+
edges {
83+
node {
84+
... on Discussion {
85+
viewerSubscription
86+
title
87+
url
88+
comments(last: 100) {
89+
edges {
90+
node {
91+
author {
92+
login
93+
avatarUrl
94+
__typename
95+
}
96+
bodyText
97+
databaseId
98+
createdAt
99+
replies(last: 1) {
100+
edges {
101+
node {
102+
databaseId
103+
createdAt
104+
author {
105+
login
106+
avatarUrl
107+
__typename
108+
}
109+
bodyText
110+
}
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}
119+
}
120+
}`
121+
}
122+
});
123+
124+
let edges =
125+
response?.data?.search?.edges?.filter(
126+
(edge) => edge.node.title === notification.subject.title
127+
) || [];
128+
if (edges.length > 1)
129+
edges = edges.filter((edge) => edge.node.viewerSubscription === 'SUBSCRIBED');
130+
131+
const comments = edges[0]?.node.comments.edges;
132+
133+
let latestCommentEdge: DiscussionSubCommentEdge | undefined;
134+
if (comments?.length) {
135+
latestCommentEdge = getLatestDiscussionCommentEdge(comments);
136+
}
137+
138+
return {
139+
url: edges[0]?.node.url,
140+
latestCommentEdge
141+
};
142+
}

0 commit comments

Comments
 (0)