feat(web): mention people with @ when replying to a comment
The reply box suggests the project's members as the main comment box does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -608,9 +608,33 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|||||||
novalidate
|
novalidate
|
||||||
>
|
>
|
||||||
<pi-textarea
|
<pi-textarea
|
||||||
[placeholder]="'Responde a ' + thread.comment.author.name + '…'"
|
[placeholder]="
|
||||||
|
'Responde a ' +
|
||||||
|
thread.comment.author.name +
|
||||||
|
'… Escribe @ para mencionar a alguien.'
|
||||||
|
"
|
||||||
[(value)]="replyBody"
|
[(value)]="replyBody"
|
||||||
/>
|
/>
|
||||||
|
@if (replyMentionOptions().length) {
|
||||||
|
<div
|
||||||
|
class="ts-mentions"
|
||||||
|
role="group"
|
||||||
|
aria-label="Mencionar a"
|
||||||
|
>
|
||||||
|
@for (member of replyMentionOptions(); track member.id) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ts-mentions__option"
|
||||||
|
(click)="mentionInReply(member)"
|
||||||
|
>
|
||||||
|
<span class="ts-comment__avatar" aria-hidden="true">{{
|
||||||
|
initialsOf(member.name)
|
||||||
|
}}</span>
|
||||||
|
{{ member.name }}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="ts-reply__actions">
|
<div class="ts-reply__actions">
|
||||||
<pi-action-button
|
<pi-action-button
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -1024,17 +1048,13 @@ export class IssuePage {
|
|||||||
protected readonly commentFiles = signal<File[]>([]);
|
protected readonly commentFiles = signal<File[]>([]);
|
||||||
protected readonly segments = mentionSegments;
|
protected readonly segments = mentionSegments;
|
||||||
/** Members matching what is typed after an `@` at the end of the comment. */
|
/** Members matching what is typed after an `@` at the end of the comment. */
|
||||||
protected readonly mentionOptions = computed(() => {
|
protected readonly mentionOptions = computed(() =>
|
||||||
const query = mentionQuery(this.commentBody());
|
this.membersMatching(this.commentBody()),
|
||||||
if (query === null) return [];
|
);
|
||||||
return this.project
|
/** The same, for the answer being written. */
|
||||||
.members()
|
protected readonly replyMentionOptions = computed(() =>
|
||||||
.map((m) => m.user)
|
this.membersMatching(this.replyBody()),
|
||||||
.filter(
|
);
|
||||||
(user) => user.id !== this.facade.me() && matches(user.name, query),
|
|
||||||
)
|
|
||||||
.slice(0, 6);
|
|
||||||
});
|
|
||||||
/** Pull requests first, then the newest. */
|
/** Pull requests first, then the newest. */
|
||||||
protected readonly devLinks = computed(() =>
|
protected readonly devLinks = computed(() =>
|
||||||
[...(this.facade.issue()?.devLinks ?? [])].sort(
|
[...(this.facade.issue()?.devLinks ?? [])].sort(
|
||||||
@@ -1226,6 +1246,23 @@ export class IssuePage {
|
|||||||
this.replyingTo.set(null);
|
this.replyingTo.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Members whose name fits the `@…` being typed at the end of `text`. */
|
||||||
|
private membersMatching(text: string) {
|
||||||
|
const query = mentionQuery(text);
|
||||||
|
if (query === null) return [];
|
||||||
|
return this.project
|
||||||
|
.members()
|
||||||
|
.map((m) => m.user)
|
||||||
|
.filter(
|
||||||
|
(user) => user.id !== this.facade.me() && matches(user.name, query),
|
||||||
|
)
|
||||||
|
.slice(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected mentionInReply(user: { id: string; name: string }): void {
|
||||||
|
this.replyBody.update((text) => insertMention(text, user));
|
||||||
|
}
|
||||||
|
|
||||||
protected mention(user: { id: string; name: string }): void {
|
protected mention(user: { id: string; name: string }): void {
|
||||||
this.commentBody.update((text) => insertMention(text, user));
|
this.commentBody.update((text) => insertMention(text, user));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user