mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 23:50:17 +00:00
fix(web-chat): normalize chat copy to plain text (#1114)
* fix(web-chat): force plain-text clipboard copy from chat messages * test(e2e): make chat copy test target deterministic message
This commit is contained in:
@@ -600,6 +600,22 @@ document.getElementById('chat-input').addEventListener('paste', (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
const chatMessagesEl = document.getElementById('chat-messages');
|
||||
chatMessagesEl.addEventListener('copy', (e) => {
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.isCollapsed) return;
|
||||
const anchorNode = selection.anchorNode;
|
||||
const focusNode = selection.focusNode;
|
||||
if (!anchorNode || !focusNode) return;
|
||||
if (!chatMessagesEl.contains(anchorNode) || !chatMessagesEl.contains(focusNode)) return;
|
||||
const text = selection.toString();
|
||||
if (!text || !e.clipboardData) return;
|
||||
// Force plain-text clipboard output so dark-theme styling never leaks on paste.
|
||||
e.preventDefault();
|
||||
e.clipboardData.clearData();
|
||||
e.clipboardData.setData('text/plain', text);
|
||||
});
|
||||
|
||||
function addGeneratedImage(dataUrl, path) {
|
||||
const container = document.getElementById('chat-messages');
|
||||
const card = document.createElement('div');
|
||||
|
||||
@@ -74,3 +74,44 @@ async def test_empty_message_not_sent(page):
|
||||
await page.wait_for_timeout(2000)
|
||||
final_count = await page.locator(f"{SEL['message_user']}, {SEL['message_assistant']}").count()
|
||||
assert final_count == initial_count, "Empty message should not create new messages"
|
||||
|
||||
|
||||
async def test_copy_from_chat_forces_plain_text(page):
|
||||
"""Copying selected chat text should populate plain text clipboard data only."""
|
||||
await page.evaluate("addMessage('assistant', 'Copy me into Sheets')")
|
||||
|
||||
copied = await page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
const content = Array.from(document.querySelectorAll('#chat-messages .message.assistant .message-content'))
|
||||
.find((el) => (el.textContent || '').includes('Copy me into Sheets'));
|
||||
if (!content) return {ok: false, reason: 'no content'};
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(content);
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
const store = {};
|
||||
const evt = new Event('copy', { bubbles: true, cancelable: true });
|
||||
evt.clipboardData = {
|
||||
clearData: () => { Object.keys(store).forEach((k) => delete store[k]); },
|
||||
setData: (t, v) => { store[t] = v; },
|
||||
getData: (t) => store[t] || '',
|
||||
};
|
||||
|
||||
content.dispatchEvent(evt);
|
||||
return {
|
||||
ok: true,
|
||||
defaultPrevented: evt.defaultPrevented,
|
||||
text: store['text/plain'] || '',
|
||||
html: store['text/html'] || '',
|
||||
};
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert copied["ok"], copied.get("reason", "copy setup failed")
|
||||
assert copied["defaultPrevented"] is True
|
||||
assert "Copy me into Sheets" in copied["text"]
|
||||
assert copied["html"] == ""
|
||||
|
||||
Reference in New Issue
Block a user