+1
-1
appview/indexer/notifier.go
+1
-1
appview/indexer/notifier.go
···
11
11
12
12
var _ notify.Notifier = &Indexer{}
13
13
14
-
func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue) {
14
+
func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
15
15
l := log.FromContext(ctx).With("notifier", "indexer", "issue", issue)
16
16
l.Debug("indexing new issue")
17
17
err := ix.Issues.Index(ctx, *issue)
+23
-2
appview/issues/issues.go
+23
-2
appview/issues/issues.go
···
24
24
"tangled.org/core/appview/notify"
25
25
"tangled.org/core/appview/oauth"
26
26
"tangled.org/core/appview/pages"
27
+
"tangled.org/core/appview/pages/markup"
27
28
"tangled.org/core/appview/pagination"
28
29
"tangled.org/core/appview/reporesolver"
29
30
"tangled.org/core/appview/validator"
···
453
454
454
455
// notify about the new comment
455
456
comment.Id = commentId
456
-
rp.notifier.NewIssueComment(r.Context(), &comment)
457
+
458
+
rawMentions := markup.FindUserMentions(comment.Body)
459
+
idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions)
460
+
l.Debug("parsed mentions", "raw", rawMentions, "idents", idents)
461
+
var mentions []syntax.DID
462
+
for _, ident := range idents {
463
+
if ident != nil && !ident.Handle.IsInvalidHandle() {
464
+
mentions = append(mentions, ident.DID)
465
+
}
466
+
}
467
+
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
457
468
458
469
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issue.IssueId, commentId))
459
470
}
···
948
959
949
960
// everything is successful, do not rollback the atproto record
950
961
atUri = ""
951
-
rp.notifier.NewIssue(r.Context(), issue)
962
+
963
+
rawMentions := markup.FindUserMentions(issue.Body)
964
+
idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions)
965
+
l.Debug("parsed mentions", "raw", rawMentions, "idents", idents)
966
+
var mentions []syntax.DID
967
+
for _, ident := range idents {
968
+
if ident != nil && !ident.Handle.IsInvalidHandle() {
969
+
mentions = append(mentions, ident.DID)
970
+
}
971
+
}
972
+
rp.notifier.NewIssue(r.Context(), issue, mentions)
952
973
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issue.IssueId))
953
974
return
954
975
}
+24
-6
appview/notify/db/db.go
+24
-6
appview/notify/db/db.go
···
64
64
// no-op
65
65
}
66
66
67
-
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
67
+
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
68
68
69
69
// build the recipients list
70
70
// - owner of the repo
···
81
81
}
82
82
83
83
actorDid := syntax.DID(issue.Did)
84
-
eventType := models.NotificationTypeIssueCreated
85
84
entityType := "issue"
86
85
entityId := issue.AtUri().String()
87
86
repoId := &issue.Repo.Id
···
91
90
n.notifyEvent(
92
91
actorDid,
93
92
recipients,
94
-
eventType,
93
+
models.NotificationTypeIssueCreated,
94
+
entityType,
95
+
entityId,
96
+
repoId,
97
+
issueId,
98
+
pullId,
99
+
)
100
+
n.notifyEvent(
101
+
actorDid,
102
+
mentions,
103
+
models.NotificationTypeUserMentioned,
95
104
entityType,
96
105
entityId,
97
106
repoId,
···
100
109
)
101
110
}
102
111
103
-
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
112
+
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
104
113
issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt))
105
114
if err != nil {
106
115
log.Printf("NewIssueComment: failed to get issues: %v", err)
···
132
141
}
133
142
134
143
actorDid := syntax.DID(comment.Did)
135
-
eventType := models.NotificationTypeIssueCommented
136
144
entityType := "issue"
137
145
entityId := issue.AtUri().String()
138
146
repoId := &issue.Repo.Id
···
142
150
n.notifyEvent(
143
151
actorDid,
144
152
recipients,
145
-
eventType,
153
+
models.NotificationTypeIssueCommented,
154
+
entityType,
155
+
entityId,
156
+
repoId,
157
+
issueId,
158
+
pullId,
159
+
)
160
+
n.notifyEvent(
161
+
actorDid,
162
+
mentions,
163
+
models.NotificationTypeUserMentioned,
146
164
entityType,
147
165
entityId,
148
166
repoId,
+4
-4
appview/notify/merged_notifier.go
+4
-4
appview/notify/merged_notifier.go
···
54
54
m.fanout("DeleteStar", ctx, star)
55
55
}
56
56
57
-
func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
58
-
m.fanout("NewIssue", ctx, issue)
57
+
func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
58
+
m.fanout("NewIssue", ctx, issue, mentions)
59
59
}
60
60
61
-
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
62
-
m.fanout("NewIssueComment", ctx, comment)
61
+
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
62
+
m.fanout("NewIssueComment", ctx, comment, mentions)
63
63
}
64
64
65
65
func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
+5
-4
appview/notify/notifier.go
+5
-4
appview/notify/notifier.go
···
13
13
NewStar(ctx context.Context, star *models.Star)
14
14
DeleteStar(ctx context.Context, star *models.Star)
15
15
16
-
NewIssue(ctx context.Context, issue *models.Issue)
17
-
NewIssueComment(ctx context.Context, comment *models.IssueComment)
16
+
NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID)
17
+
NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID)
18
18
NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue)
19
19
DeleteIssue(ctx context.Context, issue *models.Issue)
20
20
···
42
42
func (m *BaseNotifier) NewStar(ctx context.Context, star *models.Star) {}
43
43
func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {}
44
44
45
-
func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {}
46
-
func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {}
45
+
func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {}
46
+
func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
47
+
}
47
48
func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {}
48
49
func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {}
49
50
+4
-2
appview/notify/posthog/notifier.go
+4
-2
appview/notify/posthog/notifier.go
···
57
57
}
58
58
}
59
59
60
-
func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
60
+
func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
61
61
err := n.client.Enqueue(posthog.Capture{
62
62
DistinctId: issue.Did,
63
63
Event: "new_issue",
64
64
Properties: posthog.Properties{
65
65
"repo_at": issue.RepoAt.String(),
66
66
"issue_id": issue.IssueId,
67
+
"mentions": mentions,
67
68
},
68
69
})
69
70
if err != nil {
···
178
179
}
179
180
}
180
181
181
-
func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
182
+
func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
182
183
err := n.client.Enqueue(posthog.Capture{
183
184
DistinctId: comment.Did,
184
185
Event: "new_issue_comment",
185
186
Properties: posthog.Properties{
186
187
"issue_at": comment.IssueAt,
188
+
"mentions": mentions,
187
189
},
188
190
})
189
191
if err != nil {
+24
appview/pages/markup/markdown.go
+24
appview/pages/markup/markdown.go
···
302
302
return path.Join(rctx.CurrentDir, dst)
303
303
}
304
304
305
+
// FindUserMentions returns Set of user handles from given markup soruce.
306
+
// It doesn't guarntee unique DIDs
307
+
func FindUserMentions(source string) []string {
308
+
var (
309
+
mentions []string
310
+
mentionsSet = make(map[string]struct{})
311
+
md = NewMarkdown()
312
+
sourceBytes = []byte(source)
313
+
root = md.Parser().Parse(text.NewReader(sourceBytes))
314
+
)
315
+
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
316
+
if entering && n.Kind() == textension.KindAt {
317
+
handle := n.(*textension.AtNode).Handle
318
+
mentionsSet[handle] = struct{}{}
319
+
return ast.WalkSkipChildren, nil
320
+
}
321
+
return ast.WalkContinue, nil
322
+
})
323
+
for handle := range mentionsSet {
324
+
mentions = append(mentions, handle)
325
+
}
326
+
return mentions
327
+
}
328
+
305
329
func isAbsoluteUrl(link string) bool {
306
330
parsed, err := url.Parse(link)
307
331
if err != nil {