Normalize ticket mentions in editor and server

This commit is contained in:
codex-bot 2025-10-24 16:35:55 -03:00
parent cf11ac9bcb
commit 296e02cf0c
5 changed files with 447 additions and 40 deletions

View file

@ -16,7 +16,13 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { toast } from "sonner"
import { Dropzone } from "@/components/ui/dropzone"
import { RichTextEditor, RichTextContent, sanitizeEditorHtml, stripLeadingEmptyParagraphs } from "@/components/ui/rich-text-editor"
import {
RichTextEditor,
RichTextContent,
sanitizeEditorHtml,
stripLeadingEmptyParagraphs,
normalizeTicketMentionHtml,
} from "@/components/ui/rich-text-editor"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
@ -82,7 +88,8 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
}
const startEditingComment = useCallback((commentId: string, currentBody: string) => {
setEditingComment({ id: commentId, value: currentBody || "" })
const normalized = normalizeTicketMentionHtml(currentBody || "")
setEditingComment({ id: commentId, value: normalized })
}, [])
const cancelEditingComment = useCallback(() => {
@ -96,7 +103,8 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
if (commentId.startsWith("temp-")) return
const sanitized = sanitizeEditorHtml(editingComment.value)
if (sanitized === originalBody) {
const normalized = normalizeTicketMentionHtml(sanitized)
if (normalized === originalBody) {
setEditingComment(null)
return
}
@ -109,9 +117,9 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
ticketId: ticket.id as Id<"tickets">,
commentId: commentId as unknown as Id<"ticketComments">,
actorId: convexUserId as Id<"users">,
body: sanitized,
body: normalized,
})
setLocalBodies((prev) => ({ ...prev, [commentId]: sanitized }))
setLocalBodies((prev) => ({ ...prev, [commentId]: normalized }))
setEditingComment(null)
toast.success("Comentário atualizado!", { id: toastId })
} catch (error) {
@ -133,7 +141,8 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
if (!convexUserId) return
// Enforce generous max length for comment plain text
const sanitized = sanitizeEditorHtml(body)
const plain = sanitized.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim()
const normalized = normalizeTicketMentionHtml(sanitized)
const plain = normalized.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim()
const MAX_COMMENT_CHARS = 20000
if (plain.length > MAX_COMMENT_CHARS) {
toast.error(`Comentário muito longo (máx. ${MAX_COMMENT_CHARS} caracteres)`, { id: "comment" })
@ -149,7 +158,7 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
id: `temp-${now.getTime()}`,
author: ticket.requester,
visibility: selectedVisibility,
body: sanitized,
body: normalized,
attachments: attachments.map((attachment) => ({
id: attachment.storageId,
name: attachment.name,
@ -176,7 +185,7 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
ticketId: ticket.id as Id<"tickets">,
authorId: convexUserId as Id<"users">,
visibility: selectedVisibility,
body: optimistic.body,
body: normalized,
attachments: payload,
})
setPending([])