fix: improve text selection handling in copy handler for message formatting

This commit is contained in:
2026-02-22 03:58:11 +11:00
parent 42cc081d49
commit 495747af84

View File

@@ -28,54 +28,69 @@ export const setupCopyHandler = (
const messages: Array<{ sender: string; time: string; content: string }> = [];
// Get all message elements in the container
const messageElements = container.querySelectorAll('[data-message-id]');
const messageElements = Array.from(container.querySelectorAll('[data-message-id]'));
messageElements.forEach((msgEl) => {
// Check if this message element intersects with the selection
if (!selection.containsNode(msgEl, true)) return;
const eventId = msgEl.getAttribute('data-message-id');
if (!eventId) return;
const data = getMessageData(eventId);
if (!data) return;
// Get the selected text content within this message
const tempRange = document.createRange();
tempRange.selectNodeContents(msgEl);
// Find content elements within the message (skip headers, timestamps, etc.)
const contentEl = msgEl.querySelector('[class*="MessageTextBody"], [class*="message-content"], [class*="MessageBody"]') as HTMLElement;
// Check if the selection overlaps with this message
const intersects = range.compareBoundaryPoints(Range.END_TO_START, tempRange) <= 0 &&
range.compareBoundaryPoints(Range.START_TO_END, tempRange) >= 0;
if (!contentEl) return;
if (!intersects) return;
// Check if the content element intersects with the selection
// by checking if selection contains any part of it
let isSelected = false;
try {
// Try to detect if this node or its children are in the selection
const walker = document.createTreeWalker(
contentEl,
NodeFilter.SHOW_TEXT,
null
);
let textNode = walker.nextNode();
while (textNode && !isSelected) {
if (range.intersectsNode(textNode)) {
isSelected = true;
}
textNode = walker.nextNode();
}
} catch (e) {
// Fallback: check if selection contains the content element
isSelected = selection.containsNode(contentEl, true);
}
if (!isSelected) return;
// Format the time
const timeStr = timeHourMinute(data.ts, hour24Clock);
// Get the text content that was selected from this message
// Use the original content if the message is fully selected, otherwise get selected portion
// Extract the selected text from within this message's content
let selectedText = '';
// Find the message content element (the actual text, not header)
const contentElement = msgEl.querySelector('[class*="MessageTextBody"], [class*="message-content"]');
if (contentElement) {
const clonedRange = range.cloneRange();
clonedRange.selectNodeContents(contentElement);
// Create a range for this message's content
const contentRange = document.createRange();
contentRange.selectNodeContents(contentEl);
// Intersect with selection
if (range.compareBoundaryPoints(Range.START_TO_START, clonedRange) > 0) {
clonedRange.setStart(range.startContainer, range.startOffset);
// Clone the selection range to avoid modifying it
const extractRange = range.cloneRange();
// Constrain the extract range to be within the content element
// If selection starts before content, move start to content start
if (contentRange.compareBoundaryPoints(Range.START_TO_START, extractRange) > 0) {
extractRange.setStart(contentRange.startContainer, contentRange.startOffset);
}
if (range.compareBoundaryPoints(Range.END_TO_END, clonedRange) < 0) {
clonedRange.setEnd(range.endContainer, range.endOffset);
// If selection ends after content, move end to content end
if (contentRange.compareBoundaryPoints(Range.END_TO_END, extractRange) < 0) {
extractRange.setEnd(contentRange.endContainer, contentRange.endOffset);
}
selectedText = clonedRange.toString().trim();
} else {
// Fallback: use all selected text in this message
selectedText = range.toString().trim();
}
selectedText = extractRange.toString().trim();
if (selectedText) {
messages.push({