Skip to main content

Feature: Enhancing Message Interaction with Copy and Delete Options

Overview:

These changes introduce significant improvements to the MessageBubbleComponent, allowing users to interact more effectively with individual chat messages. The primary enhancements include the ability to copy message content (as plain text or Markdown), and to delete messages from the chat history. These features are implemented using Angular component communication patterns and a centralized chat service for state management.

Explanation:

Adding Copy Functionality to Messages

Situation: Previously, users could only read messages. There was no direct way to copy message content, which is a common requirement in chat applications for sharing or reusing information.

Task: Implement a copy button for each message bubble that allows users to copy the message's content to their clipboard.

Action:

  1. UI Element Introduction: A new <button class="copy-message-button"> was added within the message-bubble template. This button is designed to appear only on hover over the message bubble, ensuring a clean UI.
  2. Visual Feedback: The button's SVG icon dynamically changes between a "copy" icon and a "checkmark" icon based on a copied boolean property. This provides immediate visual feedback to the user that the content has been successfully copied. The button's styling also changes when data-copied='true' is applied.
  3. Clipboard Interaction: A copyMessageContent() method was introduced in the MessageBubbleComponent. This method retrieves the innerText of the message's content using a @ViewChild reference (messageContent) and then uses the navigator.clipboard.writeText() API to copy it.
  4. Temporary State: After a successful copy, the copied property is set to true temporarily (for 2 seconds) using setTimeout, which then reverts it to false, restoring the original copy icon.

Result: Users can now hover over any message bubble, click the appearing copy button, and the message's rendered text content will be copied to their clipboard, with a clear visual confirmation.

Providing Options for Copying and Introducing Markdown Copy

Situation: The initial copy functionality only copied the rendered plain text. For AI-generated messages, which often contain Markdown formatting (like code blocks, bold text, etc.), copying the raw Markdown can be more useful for pasting into other Markdown-aware editors.

Task: Expand the copy functionality to offer multiple options (plain text, Markdown) and provide a more structured way to add future actions.

Action:

  1. Menu Integration: The single copy button was replaced with a div.copy-options containing an options-button (using a three-dot menu icon). This options-button toggles the visibility of a div.copy-menu.
  2. Contextual Copy Options: Inside the copy-menu, two new buttons were added:
    • Copy Text: Calls copyAsText(), which copies the innerText of the rendered message content, similar to the previous implementation.
    • Copy Markdown: Calls copyAsMarkdown(), which copies the raw message.content (the original string before Markdown rendering). This option is conditionally displayed (@if (!message.isUser)) only for AI messages, as user messages are typically plain text.
  3. Clipboard Logic Refactoring: A private helper method copyToClipboard(text: string, type: string) was introduced to centralize the navigator.clipboard.writeText() call, reducing duplication. After any copy operation, showCopyMenu is set to false to close the menu.
  4. Code Block Copy Update: The existing copyToClipboard method responsible for copying code blocks was renamed to copyCodeToClipboard to avoid naming conflicts and improve clarity, specifically handling the copy action for pre-formatted code within the message.

Result: Users now have a more sophisticated copy mechanism. They can choose to copy the message as plain text or, for AI messages, as raw Markdown, catering to different use cases. The menu-based approach also provides a scalable foundation for adding more actions in the future.

Implementing Message Deletion

Situation: Beyond copying, users might need to manage their chat history by removing individual messages that are no longer relevant or were sent by mistake.

Task: Add a "Delete" option to the message context menu, allowing users to remove a specific message.

Action:

  1. Delete Button in Menu: A Delete button with a distinct red color (delete-button class) was added to the copy-menu in MessageBubbleComponent.
  2. Output Event Emission: An @Output() deleteMessage = new EventEmitter<string>(); was added to MessageBubbleComponent. When the delete button is clicked, it emits the message.id via this event. This follows Angular's best practice for child-to-parent communication.
  3. Parent Component Listener: In MessageListComponent (the parent of MessageBubbleComponent), a listener (deleteMessage)="onDeleteMessage($event)" was added to the <app-message-bubble> usage in its template.
  4. Service Integration for State Management:
    • ChatService was injected into MessageListComponent.
    • The onDeleteMessage(messageId: string) method in MessageListComponent now calls this.chatService.deleteMessage(messageId).
    • A deleteMessage(messageId: string) method was added to the ChatService. This method retrieves the current list of messages, filters out the message with the matching ID to create a new array, and then pushes this updated array to the messagesSubject (a BehaviorSubject).
    • this.updateCurrentSession() is called in the ChatService after deletion to ensure the change is persisted (e.g., in local storage).

Result: Users can now delete individual messages from their chat history. The deletion process leverages Angular's component communication for UI interaction and a centralized service for robust, immutable state management and persistence, ensuring data consistency across the application.

Enhancing Menu Usability with Outside Click Closure

Situation: With the introduction of a dropdown menu for copy and delete options, a common UX expectation is that clicking anywhere outside the menu should close it. Without this, the menu would remain open until another menu item is clicked or the same toggle button is pressed.

Task: Implement logic to automatically close the copy/options menu when a user clicks outside of it, within the bounds of the message bubble.

Action:

  1. Wrapper Click Handler: An (click)="onBubbleClick()" handler was added to the div.message-wrapper (the outermost container of the MessageBubbleComponent). The onBubbleClick() method checks if showCopyMenu is true and, if so, sets it to false. This handles clicks within the message bubble but outside the copy-options menu itself.
  2. Preventing Event Bubbling:
    • The (click) handler on the options-button was updated to (click)="toggleCopyMenu($event)", passing the MouseEvent object. Inside toggleCopyMenu(), event.stopPropagation() is now called. This is crucial: it stops the click event from bubbling up to the message-wrapper's onBubbleClick(), ensuring that clicking the menu toggle button only toggles the menu and doesn't immediately close it due to the parent's click handler.
    • Similarly, the event listener for the Copy button on code blocks within the message was updated to also include event.stopPropagation(). This prevents the onBubbleClick() from being triggered and closing the menu when a code block is copied.

Result: The options menu now closes automatically when the user clicks anywhere else on the message bubble, providing a more intuitive and user-friendly experience consistent with common dropdown patterns. The judicious use of event.stopPropagation() ensures that specific button clicks within the bubble do not inadvertently trigger the menu-closing logic.

Best Practices:

  • Component Communication: The use of @Input() for data flow down (message to MessageBubbleComponent) and @Output() EventEmitter for events up (deleteMessage from MessageBubbleComponent to MessageListComponent) is a fundamental Angular best practice, promoting a clear and maintainable component hierarchy.
  • Separation of Concerns: Business logic and state management (like message creation, deletion, and persistence) are encapsulated within the ChatService. Components (MessageBubbleComponent, MessageListComponent) are primarily responsible for UI rendering and emitting events, delegating complex operations to services.
  • Immutability with RxJS: The ChatService updates its message list by creating a new array using filter rather than directly mutating the existing array. This is critical when working with RxJS BehaviorSubjects, as it ensures that new references are emitted, which Angular's change detection (especially with OnPush strategy) can effectively detect.
  • Event Propagation Control (stopPropagation()): Correctly using event.stopPropagation() is essential for managing complex event flows in nested UI elements, preventing unwanted side effects from parent click handlers.
  • User Experience (UX): Providing visual feedback for copy actions and implementing "click outside to close" behavior for dropdowns are standard UX patterns that enhance usability and intuitiveness.
  • Standard Web APIs: Leveraging navigator.clipboard.writeText() for clipboard operations is the modern and secure way to interact with the system clipboard.

Suggestions:

  • Global Click Listener for Menu Closure: While the current implementation effectively closes the menu when clicking elsewhere within the message bubble, a more robust "click outside to close" pattern for dropdowns typically involves a @HostListener('document:click') in the MessageBubbleComponent. This listener would check if the clicked element is not within the component's ElementRef to close the menu, ensuring it closes even if the user clicks completely outside the message bubble itself. This could be considered for a more generic dropdown behavior.
  • Confirmation Dialog for Deletion: Deleting a message is a destructive action. For improved user experience and to prevent accidental deletions, consider adding a simple confirmation dialog (e.g., "Are you sure you want to delete this message?") before calling chatService.deleteMessage().
  • Accessibility (A11y): For the copy/delete menu, ensure it is keyboard-navigable and accessible for users who rely on screen readers. This might involve adding ARIA attributes (e.g., aria-haspopup, aria-expanded) and managing focus.