Files
Bonsai-webapp/src/app/issues/issue-comments/issue-comments.ts
T
2026-05-26 21:41:14 +02:00

96 lines
3.3 KiB
TypeScript

import { Component, computed, inject, input } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { marked } from 'marked';
import { handleImagePaste, insertAtSelection } from '../paste-image.util';
import { IssueComment, IssuesStore } from '../issues.store';
@Component({
selector: 'app-issue-comments',
imports: [FormsModule],
templateUrl: './issue-comments.html',
styleUrl: './issue-comments.css',
})
export class IssueComments {
private readonly issuesStore = inject(IssuesStore);
private readonly sanitizer = inject(DomSanitizer);
readonly issueId = input.required<number>();
protected readonly comments = computed(
() => this.issuesStore.issues().find((i) => i.id === this.issueId())?.comments ?? [],
);
protected newCommentText = '';
protected editingCommentId: number | null = null;
protected editingCommentText = '';
protected parseMarkdown(text: string): SafeHtml {
const html = marked.parse(text) as string;
return this.sanitizer.bypassSecurityTrustHtml(html);
}
protected formatDate(iso: string): string {
return new Date(iso).toLocaleString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}
protected async addComment(): Promise<void> {
const text = this.newCommentText.trim();
if (!text) return;
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 };
await this.issuesStore.upsert({ ...issue, comments: [...issue.comments, comment] });
this.newCommentText = '';
}
protected startEditComment(comment: IssueComment): void {
this.editingCommentId = comment.id;
this.editingCommentText = comment.text;
}
protected async saveEditComment(): Promise<void> {
const text = this.editingCommentText.trim();
if (!text || this.editingCommentId === null) return;
const issue = this.issuesStore.getById(this.issueId());
if (!issue) return;
const updatedComments = issue.comments.map((c) =>
c.id === this.editingCommentId ? { ...c, text, updatedAt: new Date().toISOString() } : c,
);
await this.issuesStore.upsert({ ...issue, comments: updatedComments });
this.editingCommentId = null;
this.editingCommentText = '';
}
protected onPaste(event: ClipboardEvent, field: 'new' | 'edit'): void {
const ta = event.target as HTMLTextAreaElement;
const start = ta.selectionStart;
const end = ta.selectionEnd;
handleImagePaste(event, (md) => {
if (field === 'new') {
this.newCommentText = insertAtSelection(ta, this.newCommentText, start, end, md);
} else {
this.editingCommentText = insertAtSelection(ta, this.editingCommentText, start, end, md);
}
});
}
protected cancelEditComment(): void {
this.editingCommentId = null;
this.editingCommentText = '';
}
protected async deleteComment(id: number): Promise<void> {
const issue = this.issuesStore.getById(this.issueId());
if (!issue) return;
await this.issuesStore.upsert({ ...issue, comments: issue.comments.filter((c) => c.id !== id) });
}
}