mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	This PR adds the ability to include the thread history as a quoted text ## Preview https://github.com/user-attachments/assets/c96a85e5-8ac8-4021-86ca-57509b4eea9f
		
			
				
	
	
		
			333 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { format, parseISO, isValid as isValidDate } from 'date-fns';
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts plain text from HTML content
 | 
						|
 * @param {string} html - HTML content to convert
 | 
						|
 * @returns {string} Plain text content
 | 
						|
 */
 | 
						|
export const extractPlainTextFromHtml = html => {
 | 
						|
  if (!html) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
  if (typeof document === 'undefined') {
 | 
						|
    return html.replace(/<[^>]*>/g, ' ');
 | 
						|
  }
 | 
						|
  const tempDiv = document.createElement('div');
 | 
						|
  tempDiv.innerHTML = html;
 | 
						|
  return tempDiv.textContent || tempDiv.innerText || '';
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts sender name from email message
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} contact - Contact object
 | 
						|
 * @returns {string} Sender name
 | 
						|
 */
 | 
						|
export const getEmailSenderName = (lastEmail, contact) => {
 | 
						|
  const senderName = lastEmail?.sender?.name;
 | 
						|
  if (senderName && senderName.trim()) {
 | 
						|
    return senderName.trim();
 | 
						|
  }
 | 
						|
 | 
						|
  const contactName = contact?.name;
 | 
						|
  return contactName && contactName.trim() ? contactName.trim() : '';
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts sender email from email message
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} contact - Contact object
 | 
						|
 * @returns {string} Sender email address
 | 
						|
 */
 | 
						|
export const getEmailSenderEmail = (lastEmail, contact) => {
 | 
						|
  const senderEmail = lastEmail?.sender?.email;
 | 
						|
  if (senderEmail && senderEmail.trim()) {
 | 
						|
    return senderEmail.trim();
 | 
						|
  }
 | 
						|
 | 
						|
  const contentAttributes =
 | 
						|
    lastEmail?.contentAttributes || lastEmail?.content_attributes || {};
 | 
						|
  const emailMeta = contentAttributes.email || {};
 | 
						|
 | 
						|
  if (Array.isArray(emailMeta.from) && emailMeta.from.length > 0) {
 | 
						|
    const fromAddress = emailMeta.from[0];
 | 
						|
    if (fromAddress && fromAddress.trim()) {
 | 
						|
      return fromAddress.trim();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const contactEmail = contact?.email;
 | 
						|
  return contactEmail && contactEmail.trim() ? contactEmail.trim() : '';
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts date from email message
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @returns {Date|null} Email date
 | 
						|
 */
 | 
						|
export const getEmailDate = lastEmail => {
 | 
						|
  const contentAttributes =
 | 
						|
    lastEmail?.contentAttributes || lastEmail?.content_attributes || {};
 | 
						|
  const emailMeta = contentAttributes.email || {};
 | 
						|
 | 
						|
  if (emailMeta.date) {
 | 
						|
    const parsedDate = parseISO(emailMeta.date);
 | 
						|
    if (isValidDate(parsedDate)) {
 | 
						|
      return parsedDate;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const createdAt = lastEmail?.created_at;
 | 
						|
  if (createdAt) {
 | 
						|
    const timestamp = Number(createdAt);
 | 
						|
    if (!Number.isNaN(timestamp)) {
 | 
						|
      const milliseconds = timestamp > 1e12 ? timestamp : timestamp * 1000;
 | 
						|
      const derivedDate = new Date(milliseconds);
 | 
						|
      if (!Number.isNaN(derivedDate.getTime())) {
 | 
						|
        return derivedDate;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return null;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Formats date for quoted email header
 | 
						|
 * @param {Date} date - Date to format
 | 
						|
 * @returns {string} Formatted date string
 | 
						|
 */
 | 
						|
export const formatQuotedEmailDate = date => {
 | 
						|
  try {
 | 
						|
    return format(date, "EEE, MMM d, yyyy 'at' p");
 | 
						|
  } catch (error) {
 | 
						|
    const fallbackDate = new Date(date);
 | 
						|
    if (!Number.isNaN(fallbackDate.getTime())) {
 | 
						|
      return format(fallbackDate, "EEE, MMM d, yyyy 'at' p");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return '';
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts inbox email address from last email message
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} inbox - Inbox object
 | 
						|
 * @returns {string} Inbox email address
 | 
						|
 */
 | 
						|
export const getInboxEmail = (lastEmail, inbox) => {
 | 
						|
  const contentAttributes =
 | 
						|
    lastEmail?.contentAttributes || lastEmail?.content_attributes || {};
 | 
						|
  const emailMeta = contentAttributes.email || {};
 | 
						|
 | 
						|
  if (Array.isArray(emailMeta.to) && emailMeta.to.length > 0) {
 | 
						|
    const toAddress = emailMeta.to[0];
 | 
						|
    if (toAddress && toAddress.trim()) {
 | 
						|
      return toAddress.trim();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const inboxEmail = inbox?.email;
 | 
						|
  return inboxEmail && inboxEmail.trim() ? inboxEmail.trim() : '';
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Builds quoted email header from contact (for incoming messages)
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} contact - Contact object
 | 
						|
 * @returns {string} Formatted header string
 | 
						|
 */
 | 
						|
export const buildQuotedEmailHeaderFromContact = (lastEmail, contact) => {
 | 
						|
  if (!lastEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const quotedDate = getEmailDate(lastEmail);
 | 
						|
  const senderEmail = getEmailSenderEmail(lastEmail, contact);
 | 
						|
 | 
						|
  if (!quotedDate || !senderEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const formattedDate = formatQuotedEmailDate(quotedDate);
 | 
						|
  if (!formattedDate) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const senderName = getEmailSenderName(lastEmail, contact);
 | 
						|
  const hasName = !!senderName;
 | 
						|
  const contactLabel = hasName
 | 
						|
    ? `${senderName} <${senderEmail}>`
 | 
						|
    : `<${senderEmail}>`;
 | 
						|
 | 
						|
  return `On ${formattedDate} ${contactLabel} wrote:`;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Builds quoted email header from inbox (for outgoing messages)
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} inbox - Inbox object
 | 
						|
 * @returns {string} Formatted header string
 | 
						|
 */
 | 
						|
export const buildQuotedEmailHeaderFromInbox = (lastEmail, inbox) => {
 | 
						|
  if (!lastEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const quotedDate = getEmailDate(lastEmail);
 | 
						|
  const inboxEmail = getInboxEmail(lastEmail, inbox);
 | 
						|
 | 
						|
  if (!quotedDate || !inboxEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const formattedDate = formatQuotedEmailDate(quotedDate);
 | 
						|
  if (!formattedDate) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const inboxName = inbox?.name;
 | 
						|
  const hasName = !!inboxName;
 | 
						|
  const inboxLabel = hasName
 | 
						|
    ? `${inboxName} <${inboxEmail}>`
 | 
						|
    : `<${inboxEmail}>`;
 | 
						|
 | 
						|
  return `On ${formattedDate} ${inboxLabel} wrote:`;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Builds quoted email header based on message type
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @param {Object} contact - Contact object
 | 
						|
 * @param {Object} inbox - Inbox object
 | 
						|
 * @returns {string} Formatted header string
 | 
						|
 */
 | 
						|
export const buildQuotedEmailHeader = (lastEmail, contact, inbox) => {
 | 
						|
  if (!lastEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  // MESSAGE_TYPE.OUTGOING = 1, MESSAGE_TYPE.INCOMING = 0
 | 
						|
  const isOutgoing = lastEmail.message_type === 1;
 | 
						|
 | 
						|
  if (isOutgoing) {
 | 
						|
    return buildQuotedEmailHeaderFromInbox(lastEmail, inbox);
 | 
						|
  }
 | 
						|
 | 
						|
  return buildQuotedEmailHeaderFromContact(lastEmail, contact);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Formats text as markdown blockquote
 | 
						|
 * @param {string} text - Text to format
 | 
						|
 * @param {string} header - Optional header to prepend
 | 
						|
 * @returns {string} Formatted blockquote
 | 
						|
 */
 | 
						|
export const formatQuotedTextAsBlockquote = (text, header = '') => {
 | 
						|
  const normalizedLines = text
 | 
						|
    ? String(text).replace(/\r\n/g, '\n').split('\n')
 | 
						|
    : [];
 | 
						|
 | 
						|
  if (!header && !normalizedLines.length) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const quotedLines = [];
 | 
						|
 | 
						|
  if (header) {
 | 
						|
    quotedLines.push(`> ${header}`);
 | 
						|
    quotedLines.push('>');
 | 
						|
  }
 | 
						|
 | 
						|
  normalizedLines.forEach(line => {
 | 
						|
    const trimmedLine = line.trimEnd();
 | 
						|
    quotedLines.push(trimmedLine ? `> ${trimmedLine}` : '>');
 | 
						|
  });
 | 
						|
 | 
						|
  return quotedLines.join('\n');
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extracts quoted email text from last email message
 | 
						|
 * @param {Object} lastEmail - Last email message object
 | 
						|
 * @returns {string} Quoted email text
 | 
						|
 */
 | 
						|
export const extractQuotedEmailText = lastEmail => {
 | 
						|
  if (!lastEmail) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  const contentAttributes =
 | 
						|
    lastEmail.contentAttributes || lastEmail.content_attributes || {};
 | 
						|
  const emailContent = contentAttributes.email || {};
 | 
						|
  const textContent = emailContent.textContent || emailContent.text_content;
 | 
						|
 | 
						|
  if (textContent?.reply) {
 | 
						|
    return textContent.reply;
 | 
						|
  }
 | 
						|
  if (textContent?.full) {
 | 
						|
    return textContent.full;
 | 
						|
  }
 | 
						|
 | 
						|
  const htmlContent = emailContent.htmlContent || emailContent.html_content;
 | 
						|
  if (htmlContent?.reply) {
 | 
						|
    return extractPlainTextFromHtml(htmlContent.reply);
 | 
						|
  }
 | 
						|
  if (htmlContent?.full) {
 | 
						|
    return extractPlainTextFromHtml(htmlContent.full);
 | 
						|
  }
 | 
						|
 | 
						|
  const fallbackContent =
 | 
						|
    lastEmail.content || lastEmail.processed_message_content || '';
 | 
						|
 | 
						|
  return fallbackContent;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Truncates text for preview display
 | 
						|
 * @param {string} text - Text to truncate
 | 
						|
 * @param {number} maxLength - Maximum length (default: 80)
 | 
						|
 * @returns {string} Truncated text
 | 
						|
 */
 | 
						|
export const truncatePreviewText = (text, maxLength = 80) => {
 | 
						|
  const preview = text.trim().replace(/\s+/g, ' ');
 | 
						|
  if (!preview) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  if (preview.length <= maxLength) {
 | 
						|
    return preview;
 | 
						|
  }
 | 
						|
  return `${preview.slice(0, maxLength - 3)}...`;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Appends quoted text to message
 | 
						|
 * @param {string} message - Original message
 | 
						|
 * @param {string} quotedText - Text to quote
 | 
						|
 * @param {string} header - Quote header
 | 
						|
 * @returns {string} Message with quoted text appended
 | 
						|
 */
 | 
						|
export const appendQuotedTextToMessage = (message, quotedText, header) => {
 | 
						|
  const baseMessage = message ? String(message) : '';
 | 
						|
  const quotedBlock = formatQuotedTextAsBlockquote(quotedText, header);
 | 
						|
 | 
						|
  if (!quotedBlock) {
 | 
						|
    return baseMessage;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!baseMessage) {
 | 
						|
    return quotedBlock;
 | 
						|
  }
 | 
						|
 | 
						|
  let separator = '\n\n';
 | 
						|
  if (baseMessage.endsWith('\n\n')) {
 | 
						|
    separator = '';
 | 
						|
  } else if (baseMessage.endsWith('\n')) {
 | 
						|
    separator = '\n';
 | 
						|
  }
 | 
						|
 | 
						|
  return `${baseMessage}${separator}${quotedBlock}`;
 | 
						|
};
 |