Update
This commit is contained in:
+32
-11
@@ -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 |
Reference in New Issue
Block a user