Normalize ticket mentions in editor and server
This commit is contained in:
parent
cf11ac9bcb
commit
296e02cf0c
5 changed files with 447 additions and 40 deletions
|
|
@ -84,13 +84,29 @@ function truncateSubject(subject: string) {
|
|||
return `${subject.slice(0, 57)}…`
|
||||
}
|
||||
|
||||
const TICKET_MENTION_ANCHOR_CLASSES =
|
||||
"ticket-mention inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-100 px-2.5 py-1 text-xs font-semibold text-neutral-800 no-underline transition hover:bg-slate-200"
|
||||
const TICKET_MENTION_REF_CLASSES = "ticket-mention-ref text-neutral-900"
|
||||
const TICKET_MENTION_SEP_CLASSES = "ticket-mention-sep text-neutral-400"
|
||||
const TICKET_MENTION_SUBJECT_CLASSES = "ticket-mention-subject max-w-[220px] truncate text-neutral-700"
|
||||
const TICKET_MENTION_DOT_BASE_CLASSES = "ticket-mention-dot inline-flex size-2 rounded-full"
|
||||
const TICKET_MENTION_STATUS_TONE: Record<TicketStatusNormalized, string> = {
|
||||
PENDING: "bg-amber-400",
|
||||
AWAITING_ATTENDANCE: "bg-sky-500",
|
||||
PAUSED: "bg-violet-500",
|
||||
RESOLVED: "bg-emerald-500",
|
||||
}
|
||||
|
||||
function buildTicketMentionAnchor(ticket: Doc<"tickets">): string {
|
||||
const reference = ticket.reference
|
||||
const subject = escapeHtml(ticket.subject ?? "")
|
||||
const truncated = truncateSubject(subject)
|
||||
const status = (ticket.status ?? "PENDING").toString().toUpperCase()
|
||||
const priority = (ticket.priority ?? "MEDIUM").toString().toUpperCase()
|
||||
return `<a data-ticket-mention="true" data-ticket-id="${String(ticket._id)}" data-ticket-reference="${reference}" data-ticket-status="${status}" data-ticket-priority="${priority}" data-ticket-subject="${subject}" href="/tickets/${String(ticket._id)}" class="ticket-mention" title="Chamado #${reference}${subject ? ` • ${subject}` : ""}"><span class="ticket-mention-dot"></span><span class="ticket-mention-ref">#${reference}</span><span class="ticket-mention-sep">•</span><span class="ticket-mention-subject">${truncated}</span></a>`
|
||||
const normalizedStatus = normalizeStatus(status)
|
||||
const dotTone = TICKET_MENTION_STATUS_TONE[normalizedStatus] ?? "bg-slate-400"
|
||||
const dotClass = `${TICKET_MENTION_DOT_BASE_CLASSES} ${dotTone}`
|
||||
return `<a data-ticket-mention="true" data-ticket-id="${String(ticket._id)}" data-ticket-reference="${reference}" data-ticket-status="${status}" data-ticket-priority="${priority}" data-ticket-subject="${subject}" status="${normalizedStatus}" href="/tickets/${String(ticket._id)}" class="${TICKET_MENTION_ANCHOR_CLASSES}" rel="noopener noreferrer" target="_self" title="Chamado #${reference}${subject ? ` • ${subject}` : ""}"><span class="${dotClass}"></span><span class="${TICKET_MENTION_REF_CLASSES}">#${reference}</span><span class="${TICKET_MENTION_SEP_CLASSES}">•</span><span class="${TICKET_MENTION_SUBJECT_CLASSES}">${truncated}</span></a>`
|
||||
}
|
||||
|
||||
function canMentionTicket(viewerRole: string, viewerId: Id<"users">, ticket: Doc<"tickets">) {
|
||||
|
|
@ -111,11 +127,11 @@ async function normalizeTicketMentions(
|
|||
viewer: { user: Doc<"users">; role: string },
|
||||
tenantId: string,
|
||||
): Promise<string> {
|
||||
if (!html || html.indexOf("data-ticket-mention") === -1) {
|
||||
if (!html || (html.indexOf("data-ticket-mention") === -1 && html.indexOf("ticket-mention") === -1)) {
|
||||
return html
|
||||
}
|
||||
|
||||
const mentionPattern = /<a\b[^>]*data-ticket-mention="true"[^>]*>[\s\S]*?<\/a>/gi
|
||||
const mentionPattern = /<a\b[^>]*(?:data-ticket-mention="true"|class="[^"]*ticket-mention[^"]*")[^>]*>[\s\S]*?<\/a>/gi
|
||||
const matches = Array.from(html.matchAll(mentionPattern))
|
||||
if (!matches.length) {
|
||||
return html
|
||||
|
|
@ -123,10 +139,23 @@ async function normalizeTicketMentions(
|
|||
|
||||
let output = html
|
||||
|
||||
const attributePattern = /(data-[\w-]+|class|href)="([^"]*)"/gi
|
||||
|
||||
for (const match of matches) {
|
||||
const full = match[0]
|
||||
const idMatch = /data-ticket-id="([^"]+)"/i.exec(full)
|
||||
const ticketIdRaw = idMatch?.[1]
|
||||
attributePattern.lastIndex = 0
|
||||
const attributes: Record<string, string> = {}
|
||||
let attrMatch: RegExpExecArray | null
|
||||
while ((attrMatch = attributePattern.exec(full)) !== null) {
|
||||
attributes[attrMatch[1]] = attrMatch[2]
|
||||
}
|
||||
|
||||
let ticketIdRaw: string | null = attributes["data-ticket-id"] ?? null
|
||||
if (!ticketIdRaw && attributes.href) {
|
||||
const hrefPath = attributes.href.split("?")[0]
|
||||
const segments = hrefPath.split("/").filter(Boolean)
|
||||
ticketIdRaw = segments.pop() ?? null
|
||||
}
|
||||
let replacement = ""
|
||||
|
||||
if (ticketIdRaw) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue