This commit is contained in:
Your Name
2026-05-16 18:44:40 +07:00
parent 7811e1f144
commit f1db444853
2 changed files with 33 additions and 12 deletions
+32 -11
View File
@@ -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 @@
<input type="checkbox" id="ai-typing" checked>
<span>Giả lập gõ như người thật (từ từ)</span>
</label>
<div class="btn-row">
<button class="primary gray" id="ai-copy">📋 Copy</button>
<button class="primary green" id="ai-paste">📥 Dán vào ô reply</button>
</div>
<div style="font-size:12px;color:#536471;text-align:center;">
💡 Nếu chưa mở ô reply, hãy bấm Reply trước!
</div>
@@ -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 => `<option value="${it.value}">${it.text}</option>`).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é.');
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB