Ajout issue dans les commentaires
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import { Component, computed, inject, input } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { marked } from 'marked';
|
||||
import { handleImagePaste, insertAtSelection } from '../paste-image.util';
|
||||
import { IssueComment, IssuesStore } from '../issues.store';
|
||||
import { IssueComment, IssueEntity, IssuesStore } from '../issues.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-issue-comments',
|
||||
imports: [FormsModule],
|
||||
imports: [FormsModule, RouterLink],
|
||||
templateUrl: './issue-comments.html',
|
||||
styleUrl: './issue-comments.css',
|
||||
})
|
||||
@@ -21,10 +22,20 @@ export class IssueComments {
|
||||
() => this.issuesStore.issues().find((i) => i.id === this.issueId())?.comments ?? [],
|
||||
);
|
||||
|
||||
protected readonly allOtherIssues = computed(() =>
|
||||
this.issuesStore.issues().filter((i) => i.id !== this.issueId()),
|
||||
);
|
||||
|
||||
protected newCommentText = '';
|
||||
protected editingCommentId: number | null = null;
|
||||
protected editingCommentText = '';
|
||||
|
||||
protected creatingTaskForCommentId: number | null = null;
|
||||
protected newTaskName = '';
|
||||
|
||||
protected linkingIssueForCommentId: number | null = null;
|
||||
protected issueSearchText = '';
|
||||
|
||||
protected parseMarkdown(text: string): SafeHtml {
|
||||
const html = marked.parse(text) as string;
|
||||
return this.sanitizer.bypassSecurityTrustHtml(html);
|
||||
@@ -46,7 +57,13 @@ export class IssueComments {
|
||||
const issue = this.issuesStore.getById(this.issueId());
|
||||
if (!issue) return;
|
||||
const nextId = Math.max(0, ...issue.comments.map((c) => c.id)) + 1;
|
||||
const comment: IssueComment = { id: nextId, text, createdAt: new Date().toISOString(), updatedAt: null };
|
||||
const comment: IssueComment = {
|
||||
id: nextId,
|
||||
text,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: null,
|
||||
linkedIssueIds: [],
|
||||
};
|
||||
await this.issuesStore.upsert({ ...issue, comments: [...issue.comments, comment] });
|
||||
this.newCommentText = '';
|
||||
}
|
||||
@@ -54,6 +71,8 @@ export class IssueComments {
|
||||
protected startEditComment(comment: IssueComment): void {
|
||||
this.editingCommentId = comment.id;
|
||||
this.editingCommentText = comment.text;
|
||||
this.creatingTaskForCommentId = null;
|
||||
this.linkingIssueForCommentId = null;
|
||||
}
|
||||
|
||||
protected async saveEditComment(): Promise<void> {
|
||||
@@ -92,4 +111,121 @@ export class IssueComments {
|
||||
if (!issue) return;
|
||||
await this.issuesStore.upsert({ ...issue, comments: issue.comments.filter((c) => c.id !== id) });
|
||||
}
|
||||
|
||||
protected getLinkedIssues(comment: IssueComment): IssueEntity[] {
|
||||
if (!comment.linkedIssueIds.length) return [];
|
||||
return comment.linkedIssueIds
|
||||
.map((id) => this.issuesStore.getById(id))
|
||||
.filter((i): i is IssueEntity => i !== undefined);
|
||||
}
|
||||
|
||||
protected filteredIssuesForLink(commentId: number): IssueEntity[] {
|
||||
const comment = this.comments().find((c) => c.id === commentId);
|
||||
const linkedIds = new Set(comment?.linkedIssueIds ?? []);
|
||||
const search = this.issueSearchText.toLowerCase();
|
||||
return this.allOtherIssues().filter(
|
||||
(i) => !linkedIds.has(i.id) && (!search || i.name.toLowerCase().includes(search)),
|
||||
);
|
||||
}
|
||||
|
||||
protected typeIcon(type: IssueEntity['type']): { letter: string; bg: string } {
|
||||
const map: Record<IssueEntity['type'], { letter: string; bg: string }> = {
|
||||
Epic: { letter: 'E', bg: '#7c3aed' },
|
||||
Bug: { letter: 'B', bg: '#dc2626' },
|
||||
Story: { letter: 'S', bg: '#16a34a' },
|
||||
Task: { letter: 'T', bg: '#2563eb' },
|
||||
Study: { letter: 'St', bg: '#6b7280' },
|
||||
'Technical Story': { letter: 'TS', bg: '#d97706' },
|
||||
};
|
||||
return map[type] ?? { letter: '?', bg: '#6b7280' };
|
||||
}
|
||||
|
||||
protected statusLabel(status: IssueEntity['status']): { label: string; bg: string; color: string } {
|
||||
const map: Record<IssueEntity['status'], { label: string; bg: string; color: string }> = {
|
||||
draft: { label: 'Brouillon', bg: '#e2e8f0', color: '#475569' },
|
||||
todo: { label: 'À faire', bg: '#dbeafe', color: '#1d4ed8' },
|
||||
'in-progress': { label: 'En cours', bg: '#ffedd5', color: '#9a3412' },
|
||||
done: { label: 'Terminé', bg: '#dcfce7', color: '#166534' },
|
||||
};
|
||||
return map[status] ?? { label: status, bg: '#e2e8f0', color: '#475569' };
|
||||
}
|
||||
|
||||
protected startCreateTask(commentId: number): void {
|
||||
this.creatingTaskForCommentId = commentId;
|
||||
this.newTaskName = '';
|
||||
this.linkingIssueForCommentId = null;
|
||||
this.issueSearchText = '';
|
||||
}
|
||||
|
||||
protected cancelCreateTask(): void {
|
||||
this.creatingTaskForCommentId = null;
|
||||
this.newTaskName = '';
|
||||
}
|
||||
|
||||
protected async createTaskForComment(commentId: number): Promise<void> {
|
||||
const name = this.newTaskName.trim();
|
||||
if (!name) return;
|
||||
const issue = this.issuesStore.getById(this.issueId());
|
||||
if (!issue) return;
|
||||
|
||||
const newTask: IssueEntity = {
|
||||
id: 0,
|
||||
type: 'Task',
|
||||
name,
|
||||
assignee: '',
|
||||
epic: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
dependsOnIds: [],
|
||||
comments: [],
|
||||
priority: 'MOYENNE',
|
||||
status: 'draft',
|
||||
progress: 0,
|
||||
};
|
||||
const created = await this.issuesStore.upsert(newTask);
|
||||
|
||||
const updatedComments = issue.comments.map((c) => {
|
||||
if (c.id !== commentId) return c;
|
||||
return { ...c, linkedIssueIds: [...c.linkedIssueIds, created.id] };
|
||||
});
|
||||
await this.issuesStore.upsert({ ...issue, comments: updatedComments });
|
||||
this.creatingTaskForCommentId = null;
|
||||
this.newTaskName = '';
|
||||
}
|
||||
|
||||
protected startLinkIssue(commentId: number): void {
|
||||
this.linkingIssueForCommentId = commentId;
|
||||
this.issueSearchText = '';
|
||||
this.creatingTaskForCommentId = null;
|
||||
this.newTaskName = '';
|
||||
}
|
||||
|
||||
protected cancelLinkIssue(): void {
|
||||
this.linkingIssueForCommentId = null;
|
||||
this.issueSearchText = '';
|
||||
}
|
||||
|
||||
protected async linkIssueToComment(commentId: number, issueId: number): Promise<void> {
|
||||
const issue = this.issuesStore.getById(this.issueId());
|
||||
if (!issue) return;
|
||||
const updatedComments = issue.comments.map((c) => {
|
||||
if (c.id !== commentId) return c;
|
||||
if (c.linkedIssueIds.includes(issueId)) return c;
|
||||
return { ...c, linkedIssueIds: [...c.linkedIssueIds, issueId] };
|
||||
});
|
||||
await this.issuesStore.upsert({ ...issue, comments: updatedComments });
|
||||
this.linkingIssueForCommentId = null;
|
||||
this.issueSearchText = '';
|
||||
}
|
||||
|
||||
protected async unlinkIssueFromComment(commentId: number, issueId: number): Promise<void> {
|
||||
const issue = this.issuesStore.getById(this.issueId());
|
||||
if (!issue) return;
|
||||
const updatedComments = issue.comments.map((c) => {
|
||||
if (c.id !== commentId) return c;
|
||||
return { ...c, linkedIssueIds: c.linkedIssueIds.filter((id) => id !== issueId) };
|
||||
});
|
||||
await this.issuesStore.upsert({ ...issue, comments: updatedComments });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user