Files
x-poster-client/chrom-ext/content.js
T
2026-05-15 10:02:53 +00:00

210 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'use strict';
let selectedTweetText = '';
// ===== A. LẮNG NGHE CHUỘT PHẢI ĐỂ LẤY TWEET TEXT =====
document.addEventListener('contextmenu', (e) => {
const article = e.target.closest('article[data-testid="tweet"]');
if (!article) {
selectedTweetText = '';
return;
}
const textContainer = article.querySelector('[data-testid="tweetText"]');
selectedTweetText = textContainer ? textContainer.innerText.trim() : '';
});
// ===== B. LẮNG NGHE MESSAGE TỪ BACKGROUND =====
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
if (req.action === 'OPEN_FORM') {
if (!selectedTweetText) {
alert('Không tìm thấy nội dung tweet. Hãy chuột phải vào vùng văn bản của tweet.');
return;
}
openModal(selectedTweetText);
} else if (req.action === 'SHOW_RESULT') {
showResult(req.comment);
} else if (req.action === 'SHOW_ERROR') {
showError(req.error);
}
});
// ===== C. MỞ MODAL (DÙNG SHADOW DOM ĐỂ TRÁNH CSS XUNG ĐỘT) =====
function openModal(tweetText) {
removeModal(); // Xóa cũ nếu có
const host = document.createElement('div');
host.id = 'x-comment-ai-host';
Object.assign(host.style, {
position: 'fixed',
top: '0',
left: '0',
width: '100%',
height: '100%',
zIndex: '999999',
pointerEvents: 'none'
});
document.body.appendChild(host);
const shadow = host.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
.overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.55);
display: flex; align-items: center; justify-content: center;
pointer-events: auto;
}
.modal {
background: #fff; color: #0f1419;
width: 420px; max-width: 92vw;
border-radius: 16px; padding: 24px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
box-shadow: 0 25px 50px rgba(0,0,0,0.25);
display: flex; flex-direction: column; gap: 14px;
animation: popIn 0.2s ease-out;
}
@keyframes popIn { from {opacity:0; transform:scale(0.96)} to {opacity:1; transform:scale(1)} }
h3 { margin: 0; font-size: 20px; }
.tweet-box {
background: #f7f9f9; border: 1px solid #cfd9de; border-radius: 10px;
padding: 12px; font-size: 14px; line-height: 1.4;
max-height: 120px; overflow-y: auto; color: #333;
}
label { font-size: 13px; font-weight: 700; color: #536471; }
select, button {
width: 100%; padding: 10px 12px; border-radius: 8px;
border: 1px solid #cfd9de; font-size: 14px; outline: none;
}
button.primary {
background: #1d9bf0; color: #fff; border: none;
font-weight: 700; cursor: pointer; margin-top: 4px;
}
button.primary:hover { background: #1a8cd8; }
button.primary:disabled { background: #8ecdf7; cursor: default; }
button.ghost {
background: transparent; color: #536471; border: 1px solid #cfd9de; margin-top: 6px;
}
.status {
display: none; padding: 12px; border-radius: 10px; font-size: 14px; line-height: 1.5; white-space: pre-wrap;
}
.status.ok { background: #e8f6fe; border: 1px solid #b4dffc; color: #0f1419; }
.status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; }
.copy-hint { font-size: 12px; color: #536471; margin-top: 6px; text-align: center; }
`;
const overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.addEventListener('click', (e) => {
if (e.target === overlay) removeModal();
});
const modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = `
<h3>✍️ Viết Comment AI</h3>
<div>
<label>Nội dung Tweet</label>
<div class="tweet-box" id="ai-tweet"></div>
</div>
<div>
<label>Ngôn ngữ (Language)</label>
<select id="ai-language">
<option value="en">Anh</option>
<option value="ja">Nhật</option>
<option value="vi">Việt</option>
</select>
</div>
<div>
<label>Tone (Giọng điệu)</label>
<select id="ai-tone">
<option value="funny">😂 Hài hước</option>
<option value="professional">💼 Chuyên nghiệp</option>
<option value="friendly">😊 Thân thiện</option>
<option value="sarcastic">🌶️ Châm biếm</option>
<option value="neutral">😐 Trung lập</option>
<option value="excited">🤩 Hào hứng</option>
</select>
</div>
<div>
<label>Angle (Góc tiếp cận)</label>
<select id="ai-angle">
<option value="agree">👍 Đồng tình</option>
<option value="disagree">👎 Phản biện</option>
<option value="question">❓ Đặt câu hỏi</option>
<option value="add_info"> Bổ sung thông tin</option>
<option value="joke">🃏 Câu đùa / Meme</option>
</select>
</div>
<button class="primary" id="ai-run">🚀 Tạo Comment</button>
<div class="status" id="ai-status"></div>
<div class="copy-hint" id="ai-hint" style="display:none;">📋 Copy kết quả và tự dán vào ô comment nhé!</div>
<button class="ghost" id="ai-close">Đóng</button>
`;
overlay.appendChild(modal);
shadow.appendChild(style);
shadow.appendChild(overlay);
// Điền text tweet
shadow.getElementById('ai-tweet').textContent = tweetText;
// Events
shadow.getElementById('ai-close').addEventListener('click', removeModal);
shadow.getElementById('ai-run').addEventListener('click', () => {
const language = shadow.getElementById('ai-language').value;
const tone = shadow.getElementById('ai-tone').value;
const angle = shadow.getElementById('ai-angle').value;
const status = shadow.getElementById('ai-status');
const btn = shadow.getElementById('ai-run');
btn.disabled = true;
status.style.display = 'block';
status.className = 'status ok';
status.innerHTML = '<em>⏳ Đang tạo comment...</em>';
chrome.runtime.sendMessage({
action: 'GENERATE_COMMENT',
data: { text: tweetText, tone, angle, language }
});
});
}
// ===== D. HIỂN THỊ KẾT QUẢ / LỖI =====
function showResult(comment) {
const host = document.getElementById('x-comment-ai-host');
if (!host) return;
const s = host.shadowRoot;
const status = s.getElementById('ai-status');
const btn = s.getElementById('ai-run');
const hint = s.getElementById('ai-hint');
status.style.display = 'block';
status.className = 'status ok';
status.textContent = comment;
hint.style.display = 'block';
if (btn) btn.disabled = false;
}
function showError(msg) {
const host = document.getElementById('x-comment-ai-host');
if (!host) return;
const s = host.shadowRoot;
const status = s.getElementById('ai-status');
const btn = s.getElementById('ai-run');
status.style.display = 'block';
status.className = 'status err';
status.textContent = 'Lỗi: ' + msg;
if (btn) btn.disabled = false;
}
function removeModal() {
const el = document.getElementById('x-comment-ai-host');
if (el) el.remove();
}
})();