diff --git a/chrom-ext/content.js b/chrom-ext/content.js index 0f32c8b..f46b891 100644 --- a/chrom-ext/content.js +++ b/chrom-ext/content.js @@ -60,7 +60,7 @@ return [...TONE_BASE]; } function getAngles(tone) { - return tone === 'EMPATHETIC' ? [...ANGLE_DEFAULT,...ANGLE_EMPATHY] : [...ANGLE_DEFAULT]; + return tone === 'EMPATHETIC' ? [...ANGLE_DEFAULT, ...ANGLE_EMPATHY] : [...ANGLE_DEFAULT]; } // ===== A. CAPTURE TWEET ===== @@ -99,9 +99,8 @@ return false; }); - // ===== C. TYPING ENGINE (VIẾT LẠI) ===== + // ===== C. TYPING ENGINE ===== async function simulateHumanTyping(fullText) { - // Tìm ô reply của X let editor = document.querySelector('[data-testid="tweetTextarea_0"]') || document.querySelector('div[contenteditable="true"][role="textbox"]'); @@ -114,7 +113,6 @@ editor.focus(); editor.click(); - // Reset sạch: xóa hết, tạo 1 TextNode duy nhất để dễ manipulate editor.textContent = ''; const textNode = document.createTextNode(''); editor.appendChild(textNode); @@ -128,17 +126,13 @@ for (let i = 0; i < fullText.length; i++) { const char = fullText[i]; - - // 1. Thêm ký tự vào TextNode textNode.nodeValue += char; - // 2. Đẩy cursor về cuối range.setEnd(textNode, textNode.nodeValue.length); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); - // 3. Báo cho React/Draft.js biết DOM đã đổi editor.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, @@ -146,7 +140,6 @@ data: char })); - // 4. Delay như người gõ thật let delay = 30 + Math.random() * 50; if (char === ' ') delay += 40 + Math.random() * 40; if ('.!?,'.includes(char)) delay += 120 + Math.random() * 200; @@ -154,7 +147,6 @@ await new Promise(r => setTimeout(r, delay)); } - // Final change event editor.dispatchEvent(new Event('change', { bubbles: true })); return true; } @@ -206,6 +198,8 @@ button.primary:disabled { background: #8ecdf7; cursor: default; } button.green { background: #17bf63 !important; } button.green:hover { background: #15a857 !important; } + button.gray { background: #536471 !important; } + button.gray:hover { background: #3e4e56 !important; } .status { display: none; padding: 12px; border-radius: 10px; font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; } .status.ok { background: #e8f6fe; border: 1px solid #b4dffc; } @@ -216,6 +210,8 @@ .typing-opts.visible { display: flex; } .check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #0f1419; cursor: pointer; } .check-row input { cursor: pointer; } + .btn-row { display: flex; gap: 8px; } + .btn-row .primary { flex: 1; margin-top: 0; } `; const fab = document.createElement('button'); @@ -254,7 +250,10 @@ Giả lập gõ như người thật (từ từ) - +
+ + +
💡 Nếu chưa mở ô reply, hãy bấm Reply trước!
@@ -311,6 +310,7 @@ const typingOpts = drawer.querySelector('#ai-typing-opts'); const typingChk = drawer.querySelector('#ai-typing'); const pasteBtn = drawer.querySelector('#ai-paste'); + const copyBtn = drawer.querySelector('#ai-copy'); function populateSelect(sel, items, selectedValue) { sel.innerHTML = items.map(it => ``).join(''); @@ -365,6 +365,27 @@ ); }); + // COPY BUTTON + copyBtn.addEventListener('click', async () => { + const text = statusEl.textContent; + if (!text || text.startsWith('⏳') || text.startsWith('Lỗi') || text.startsWith('⚠️')) { + const old = copyBtn.textContent; + copyBtn.textContent = '⚠️ Chưa có nội dung!'; + setTimeout(() => copyBtn.textContent = old, 1200); + return; + } + try { + await navigator.clipboard.writeText(text); + const old = copyBtn.textContent; + copyBtn.textContent = '✅ Đã copy!'; + setTimeout(() => copyBtn.textContent = old, 1200); + } catch (e) { + const old = copyBtn.textContent; + copyBtn.textContent = '❌ Lỗi copy'; + setTimeout(() => copyBtn.textContent = old, 1200); + } + }); + // PASTE BUTTON pasteBtn.addEventListener('click', async () => { const text = statusEl.textContent; @@ -380,7 +401,7 @@ if (typingChk.checked) { pasteBtn.textContent = '⌨️ Đang gõ...'; const ok = await simulateHumanTyping(text); - if (ok) drawer.classList.remove('open'); // thu sidebar để người dùng thấy reply + if (ok) drawer.classList.remove('open'); } else { await navigator.clipboard.writeText(text); alert('✅ Đã copy vào clipboard!\n\nBạn tự dán vào ô reply nhé.'); diff --git a/chrom-ext/icons/icon.png b/chrom-ext/icons/icon.png new file mode 100644 index 0000000..a014abc Binary files /dev/null and b/chrom-ext/icons/icon.png differ