MessageList.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import type { Message } from '@extension/storage';
  2. import { ACTOR_PROFILES } from '../types/message';
  3. import { memo } from 'react';
  4. interface MessageListProps {
  5. messages: Message[];
  6. isDarkMode?: boolean;
  7. }
  8. export default memo(function MessageList({ messages, isDarkMode = false }: MessageListProps) {
  9. return (
  10. <div className="max-w-full space-y-4">
  11. {messages.map((message, index) => (
  12. <MessageBlock
  13. key={`${message.actor}-${message.timestamp}-${index}`}
  14. message={message}
  15. isSameActor={index > 0 ? messages[index - 1].actor === message.actor : false}
  16. isDarkMode={isDarkMode}
  17. />
  18. ))}
  19. </div>
  20. );
  21. });
  22. interface MessageBlockProps {
  23. message: Message;
  24. isSameActor: boolean;
  25. isDarkMode?: boolean;
  26. }
  27. function MessageBlock({ message, isSameActor, isDarkMode = false }: MessageBlockProps) {
  28. if (!message.actor) {
  29. console.error('No actor found');
  30. return <div />;
  31. }
  32. const actor = ACTOR_PROFILES[message.actor as keyof typeof ACTOR_PROFILES];
  33. const isProgress = message.content === 'Showing progress...';
  34. return (
  35. <div
  36. className={`flex max-w-full gap-3 ${
  37. !isSameActor
  38. ? `mt-4 border-t ${isDarkMode ? 'border-sky-800/50' : 'border-sky-200/50'} pt-4 first:mt-0 first:border-t-0 first:pt-0`
  39. : ''
  40. }`}>
  41. {!isSameActor && (
  42. <div
  43. className="flex size-8 shrink-0 items-center justify-center rounded-full"
  44. style={{ backgroundColor: actor.iconBackground }}>
  45. <img src={actor.icon} alt={actor.name} className="size-6" />
  46. </div>
  47. )}
  48. {isSameActor && <div className="w-8" />}
  49. <div className="min-w-0 flex-1">
  50. {!isSameActor && (
  51. <div className={`mb-1 text-sm font-semibold ${isDarkMode ? 'text-gray-200' : 'text-gray-900'}`}>
  52. {actor.name}
  53. </div>
  54. )}
  55. <div className="space-y-0.5">
  56. <div className={`whitespace-pre-wrap break-words text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
  57. {isProgress ? (
  58. <div className={`h-1 overflow-hidden rounded ${isDarkMode ? 'bg-gray-700' : 'bg-gray-200'}`}>
  59. <div className="h-full animate-progress bg-blue-500" />
  60. </div>
  61. ) : (
  62. message.content
  63. )}
  64. </div>
  65. {!isProgress && (
  66. <div className={`text-right text-xs ${isDarkMode ? 'text-gray-500' : 'text-gray-300'}`}>
  67. {formatTimestamp(message.timestamp)}
  68. </div>
  69. )}
  70. </div>
  71. </div>
  72. </div>
  73. );
  74. }
  75. /**
  76. * Formats a timestamp (in milliseconds) to a readable time string
  77. * @param timestamp Unix timestamp in milliseconds
  78. * @returns Formatted time string
  79. */
  80. function formatTimestamp(timestamp: number): string {
  81. const date = new Date(timestamp);
  82. const now = new Date();
  83. // Check if the message is from today
  84. const isToday = date.toDateString() === now.toDateString();
  85. // Check if the message is from yesterday
  86. const yesterday = new Date(now);
  87. yesterday.setDate(yesterday.getDate() - 1);
  88. const isYesterday = date.toDateString() === yesterday.toDateString();
  89. // Check if the message is from this year
  90. const isThisYear = date.getFullYear() === now.getFullYear();
  91. // Format the time (HH:MM)
  92. const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  93. if (isToday) {
  94. return timeStr; // Just show the time for today's messages
  95. }
  96. if (isYesterday) {
  97. return `Yesterday, ${timeStr}`;
  98. }
  99. if (isThisYear) {
  100. // Show month and day for this year
  101. return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })}, ${timeStr}`;
  102. }
  103. // Show full date for older messages
  104. return `${date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })}, ${timeStr}`;
  105. }