您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao
当前为
// ==UserScript== // @name TTV Auto Upload // @namespace http://tampermonkey.net/ // @version 3.7 // @description Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao // @author HA // @match https://tangthuvien.net/dang-chuong/story/* // @grant none // ==/UserScript== (function() { 'use strict'; // CSS cho thông báo và control panel const style = document.createElement('style'); style.textContent = ` .ttv-notification { position: fixed; top: 20px; right: 20px; padding: 10px 20px; background: #4CAF50; color: white; border-radius: 4px; z-index: 9999; display: none; } .ttv-error { background: #f44336; } .ttv-control-panel { position: fixed; top: 50px; right: 20px; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); z-index: 9998; width: 500px; margin-bottom: 20px; transition: all 0.3s ease; } .ttv-control-panel.minimized { width: auto; height: auto; padding: 10px; opacity: 0.8; transform: translateX(calc(100% - 40px)); transition: all 0.3s ease; } .ttv-control-panel.minimized:hover { opacity: 1; transform: translateX(0); } .ttv-control-panel.minimized .ttv-button-group, .ttv-control-panel.minimized .ttv-header { display: none; } .ttv-button-group { display: flex; flex-direction: column; gap: 15px; } .ttv-content-editor { width: 100%; height: 100px; margin: 6px 0; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; line-height: 1.4; resize: vertical; transition: all 0.2s ease; } .ttv-content-editor:focus { border-color: #5bc0de; outline: none; box-shadow: 0 0 8px rgba(91,192,222,0.2); } .ttv-preview { display: none; width: 100%; height: 100px; margin: 6px 0; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; line-height: 1.4; overflow-y: auto; background: #f9f9f9; transition: all 0.2s ease; } .ttv-heading { font-size: 15px; color: #555; margin: 12px 0; display: flex; justify-content: space-between; align-items: center; } .ttv-word-count { font-size: 12px; color: #888; padding: 3px 8px; background: #f5f5f5; border-radius: 4px; transition: all 0.2s ease; } .ttv-chapter-list { width: 100%; margin: 10px 0; max-height: 200px; overflow-y: auto; border: 1px solid #eee; border-radius: 6px; padding: 8px; } .ttv-chapter-item { padding: 8px; border-bottom: 1px solid #eee; cursor: pointer; transition: all 0.2s ease; line-height: 1.4; font-size: 12px; color: #666; } .ttv-chapter-item .chapter-title { font-weight: bold; margin-bottom: 4px; } .ttv-chapter-item .chapter-name { color: #888; padding-left: 10px; border-left: 2px solid #ddd; margin: 4px 0; } .ttv-chapter-item .chapter-stats { font-size: 11px; color: #999; } .ttv-chapter-item:last-child { border-bottom: none; } .ttv-chapter-item.selected { background: #f0f8ff; border-left: 2px solid #5bc0de; } .ttv-control-panel.fullscreen { position: fixed; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; border-radius: 0; z-index: 9999; padding: 15px; display: flex; flex-direction: column; } .ttv-control-panel.fullscreen .ttv-content-editor, .ttv-control-panel.fullscreen .ttv-preview { height: calc(100vh - 250px); margin: 15px 0; font-size: 16px; } .ttv-header { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #eee; font-weight: bold; font-size: 16px; color: #444; display: flex; justify-content: space-between; align-items: center; } button.btn-warning { background: #f0ad4e; color: white; border: none; padding: 12px; border-radius: 6px; width: 100%; font-size: 14px; transition: all 0.2s ease; } button.btn-warning:hover { background: #ec971f; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } button.ttv-minimize { padding: 2px 8px; background: none; border: none; cursor: pointer; font-size: 16px; color: #666; transition: all 0.2s ease; } button.ttv-minimize:hover { color: #333; } `; document.head.appendChild(style); // Tạo div thông báo const notification = document.createElement('div'); notification.className = 'ttv-notification'; document.body.appendChild(notification); // Hiển thị thông báo function showNotification(message, isError = false) { notification.textContent = message; notification.className = 'ttv-notification' + (isError ? ' ttv-error' : ''); notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } // Parse chapters function update function parseChapters(content) { const lines = content.split('\n'); const chapters = []; let currentChapter = { title: '', name: '', content: [] }; const chapterPattern = /^\s*Chương\s+\d+:/; let previousChapterTitle = ''; // Find chapters with clear markers for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (chapterPattern.test(line)) { // Check if it's a duplicate header if (line !== previousChapterTitle) { // Log for debug console.log(`Tìm thấy chương mới: ${line}`); // If there was a previous chapter, save it if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); console.log(`Đã lưu chương: ${currentChapter.title}\nTên: ${currentChapter.name}\nSố dòng: ${currentChapter.content.length}`); currentChapter = {title: '', name: '', content: []}; } // Save current chapter title currentChapter.title = line; // Get chapter name after : let name = line.split(':')[1]?.trim() || ''; // Remove punctuation from start and end of chapter name name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim(); currentChapter.name = name; previousChapterTitle = line; console.log(`Đang xử lý chương mới:\nTiêu đề: ${line}\nTên chương: ${name}`); } else { console.log(`Bỏ qua tiêu đề trùng lặp: ${line}`); } } else { // Save all non-chapter-title content if (line) { // Only add non-empty lines if (currentChapter.title) { currentChapter.content.push(line); } } } } // Add the last chapter if exists if (currentChapter.title && currentChapter.content.length > 0) { chapters.push({...currentChapter}); } // Display chapters and auto fill forms if (chapters.length > 0) { displayChapters(chapters); // Auto fill forms setTimeout(() => { try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } }, 1000); } console.log(`Tổng số chương tìm thấy: ${chapters.length}`); return chapters; } // Thêm function createChapterHTML function createChapterHTML(chapNum) { const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; const chap_vol_name = jQuery('.chap_vol_name').val() || ''; return ` <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK"> <div class="col-xs-12 form-group"></div> <div class="form-group"> <label class="col-sm-2" for="chap_stt">STT</label> <div class="col-sm-8"> <input class="form-control" required name="chap_stt[${chapNum}]" value="${dăngnhanhTTV.STATE.CHAP_STT}" placeholder="Số thứ tự của chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_number">Chương thứ..</label> <div class="col-sm-8"> <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Quyển số</label> <div class="col-sm-8"> <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên quyển</label> <div class="col-sm-8"> <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" /> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên chương</label> <div class="col-sm-8"> <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="introduce">Nội dung</label> <div class="col-sm-8"> <textarea maxlength="75000" style="color:#000;font-weight: 400;" required class="form-control" name="introduce[${chapNum}]" rows="20" placeholder="Nội dung" type="text"></textarea> <div class="chapter-character-count"></div> </div> </div> <div class="form-group"> <label class="col-sm-2" for="adv">Quảng cáo</label> <div class="col-sm-8"> <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea> </div> </div> </div>`; } // Add state management const dăngnhanhTTV = { STATE: { CHAP_NUMBER: 1, CHAP_STT: 1, CHAP_SERIAL: 1, CHAP_NUMBER_ORIGINAL: 1, CHAP_STT_ORIGINAL: 1, CHAP_SERIAL_ORIGINAL: 1 }, initializeChapterValues: function() { try { const chap_number = parseInt(jQuery('#chap_number').val()); let chap_stt = parseInt(jQuery('.chap_stt1').val()); let chap_serial = parseInt(jQuery('.chap_serial').val()); if (parseInt(jQuery('#chap_stt').val()) > chap_stt) { chap_stt = parseInt(jQuery('#chap_stt').val()); } if (parseInt(jQuery('#chap_serial').val()) > chap_serial) { chap_serial = parseInt(jQuery('#chap_serial').val()); } this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1; this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1; this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1; } catch (e) { console.error("Error initializing chapter values:", e); } } }; // Update transferContent function to use createChapterHTML async function transferContent() { try { console.log("Bắt đầu quá trình chuyển nội dung"); // Get all chapters const chapterItems = document.querySelectorAll('.ttv-chapter-item'); if (!chapterItems.length) { throw new Error('Không tìm thấy chương nào để điền vào form'); } console.log(`Tìm thấy ${chapterItems.length} chương để điền vào form`); // Find the form container const form = document.querySelector('form[name="postChapForm"]'); if (!form) { throw new Error('Không tìm thấy form đăng chương'); } console.log("Tìm thấy form đăng chương"); // Get and update state values let chap_vol = 1; let chap_vol_name = ''; try { chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; chap_vol_name = jQuery('.chap_vol_name').val() || ''; console.log(`Đã lấy thông tin quyển: số ${chap_vol}, tên: ${chap_vol_name}`); } catch (e) { console.warn("Không thể lấy thông tin quyển, sử dụng giá trị mặc định", e); } // Tìm STT chương và số thứ tự cao nhất trong form hiện tại let maxChapStt = 0; let maxChapSerial = 0; try { // Tìm tất cả các form chương hiện có const existingForms = form.querySelectorAll('[id^="COUNT_CHAP_"]'); console.log(`Tìm thấy ${existingForms.length} form chương hiện có`); // Tìm STT và số thứ tự cao nhất existingForms.forEach(formElem => { try { // Lấy index từ ID của form, ví dụ: COUNT_CHAP_1_MK -> 1 const formIdMatch = formElem.id.match(/COUNT_CHAP_(\d+)_MK/); if (formIdMatch && formIdMatch[1]) { const formIndex = parseInt(formIdMatch[1]); // Lấy giá trị STT const sttInput = formElem.querySelector(`input[name="chap_stt[${formIndex}]"]`); if (sttInput && sttInput.value && !isNaN(parseInt(sttInput.value))) { const sttVal = parseInt(sttInput.value); if (sttVal > maxChapStt) { maxChapStt = sttVal; } } // Lấy giá trị số thứ tự const serialInput = formElem.querySelector(`input[name="chap_number[${formIndex}]"]`); if (serialInput && serialInput.value && !isNaN(parseInt(serialInput.value))) { const serialVal = parseInt(serialInput.value); if (serialVal > maxChapSerial) { maxChapSerial = serialVal; } } } } catch (formError) { console.warn("Lỗi khi đọc giá trị từ form:", formError); } }); console.log(`STT chương cao nhất tìm thấy: ${maxChapStt}, Số thứ tự cao nhất: ${maxChapSerial}`); // Cập nhật state với giá trị cao nhất tìm được if (maxChapStt > 0) { dăngnhanhTTV.STATE.CHAP_STT = maxChapStt; } if (maxChapSerial > 0) { dăngnhanhTTV.STATE.CHAP_SERIAL = maxChapSerial; } } catch (e) { console.warn("Không thể tìm STT và số thứ tự cao nhất, sử dụng giá trị mặc định", e); } // Đếm số form chương hiện có để xác định index bắt đầu const existingFormCount = form.querySelectorAll('[id^="COUNT_CHAP_"]').length; console.log(`Số form chương hiện có: ${existingFormCount}`); console.log(`Bắt đầu tạo ${chapterItems.length} form chương mới từ index ${existingFormCount + 1}`); // Tạo form cho mỗi chương for (let i = 0; i < chapterItems.length; i++) { const formIndex = existingFormCount + i + 1; console.log(`Đang tạo form cho chương ${formIndex}`); try { // Tăng các giá trị STT và số thứ tự cho chương mới dăngnhanhTTV.STATE.CHAP_STT++; dăngnhanhTTV.STATE.CHAP_SERIAL++; console.log(`Sử dụng STT: ${dăngnhanhTTV.STATE.CHAP_STT}, Số thứ tự: ${dăngnhanhTTV.STATE.CHAP_SERIAL}`); // Tạo HTML cho form chương mới const chapterHTML = createChapterHTML(formIndex); const tempDiv = document.createElement('div'); tempDiv.innerHTML = chapterHTML; const newFormElement = tempDiv.firstElementChild; if (!newFormElement) { throw new Error(`Không thể tạo element form cho chương ${formIndex}`); } form.appendChild(newFormElement); console.log(`Đã thêm form HTML cho chương ${formIndex}`); // Lấy thông tin chương const chapterItem = chapterItems[i]; if (!chapterItem) { throw new Error(`Không tìm thấy dữ liệu cho chương ${formIndex}`); } const titleElement = chapterItem.querySelector('.chapter-title'); const nameElement = chapterItem.querySelector('.chapter-name'); if (!titleElement || !nameElement) { throw new Error(`Thiếu thông tin tiêu đề hoặc tên cho chương ${formIndex}`); } const chapterTitle = titleElement.textContent; const chapterName = nameElement.textContent.replace('Tên chương: ', ''); const chapterNumberMatch = chapterTitle.match(/Chương\s+(\d+)/); const chapterNumber = chapterNumberMatch ? chapterNumberMatch[1] : dăngnhanhTTV.STATE.CHAP_SERIAL.toString(); console.log(`Thông tin chương ${formIndex}: Tiêu đề: ${chapterTitle}, Tên: ${chapterName}, Số: ${chapterNumber}`); // Lấy các trường form const formFields = { chapterName: form.querySelector(`input[name="chap_name[${formIndex}]"]`), content: form.querySelector(`textarea[name="introduce[${formIndex}]"]`), chapterNumber: form.querySelector(`input[name="chap_number[${formIndex}]"]`), chapterOrder: form.querySelector(`input[name="chap_stt[${formIndex}]"]`), volume: form.querySelector(`input[name="vol[${formIndex}]"]`), volumeName: form.querySelector(`input[name="vol_name[${formIndex}]"]`), advertisement: form.querySelector(`textarea[name="adv[${formIndex}]"]`) }; // Kiểm tra xem có thiếu trường nào không const missingFields = []; for (const [fieldName, element] of Object.entries(formFields)) { if (!element) { missingFields.push(fieldName); } } if (missingFields.length > 0) { throw new Error(`Thiếu các trường ${missingFields.join(', ')} trong form ${formIndex}`); } // Điền dữ liệu vào form formFields.chapterName.value = chapterName; formFields.chapterNumber.value = dăngnhanhTTV.STATE.CHAP_SERIAL.toString(); formFields.chapterOrder.value = dăngnhanhTTV.STATE.CHAP_STT.toString(); formFields.volume.value = chap_vol; formFields.volumeName.value = chap_vol_name; // Kiểm tra và gán nội dung if (chapterItem._content) { formFields.content.value = chapterItem._content; } else { console.warn(`Chương ${formIndex} không có nội dung`); formFields.content.value = ''; } formFields.advertisement.value = ''; console.log(`Đã điền form cho chương ${formIndex}: Tên: ${chapterName}, STT: ${dăngnhanhTTV.STATE.CHAP_STT}, Số thứ tự: ${dăngnhanhTTV.STATE.CHAP_SERIAL}, Độ dài nội dung: ${formFields.content.value.length} ký tự`); // Kích hoạt sự kiện input để cập nhật số đếm ký tự try { const inputEvent = new Event('input', { bubbles: true }); formFields.content.dispatchEvent(inputEvent); } catch (inputError) { console.warn(`Không thể kích hoạt sự kiện input cho chương ${formIndex}:`, inputError); } } catch (chapterError) { console.error(`Lỗi khi xử lý chương ${formIndex}:`, chapterError); showNotification(`Lỗi ở chương ${formIndex}: ${chapterError.message}`, true); // Tiếp tục với chương tiếp theo thay vì dừng toàn bộ quá trình } } showNotification(`Đã tự động điền ${chapterItems.length} chương vào form!`); console.log(`Đã hoàn thành việc điền ${chapterItems.length} chương vào form`); } catch (error) { console.error('Lỗi khi điền form:', error); showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true); } } // Chọn chương để hiển thị function selectChapter(chapter, index) { const contentEditor = document.querySelector('.ttv-content-editor'); contentEditor.value = chapter.title + '\n' + chapter.content.join('\n'); // Cập nhật số từ const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } // Highlight selected chapter const chapterItems = document.querySelectorAll('.ttv-chapter-item'); chapterItems.forEach(item => item.classList.remove('selected')); chapterItems[index]?.classList.add('selected'); // Tự động điền form khi chọn chương try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } } // Hiển thị danh sách chương function displayChapters(chapters) { const chapterList = document.createElement('div'); chapterList.className = 'ttv-chapter-list'; chapters.forEach((chapter, index) => { const chapterItem = document.createElement('div'); chapterItem.className = 'ttv-chapter-item'; // Store chapter content chapterItem._content = chapter.title + '\n' + chapter.content.join('\n'); // Tạo các phần tử con với định dạng riêng const titleDiv = document.createElement('div'); titleDiv.className = 'chapter-title'; titleDiv.textContent = chapter.title; const nameDiv = document.createElement('div'); nameDiv.className = 'chapter-name'; nameDiv.textContent = `Tên chương: ${chapter.name}`; const statsDiv = document.createElement('div'); statsDiv.className = 'chapter-stats'; statsDiv.textContent = `${chapter.content.length} dòng`; // Thêm các phần tử vào item chapterItem.appendChild(titleDiv); chapterItem.appendChild(nameDiv); chapterItem.appendChild(statsDiv); chapterItem.onclick = () => selectChapter(chapter, index); chapterList.appendChild(chapterItem); // Select first chapter by default if (index === 0) { chapterItem.classList.add('selected'); } }); const existingList = document.querySelector('.ttv-chapter-list'); if (existingList) { existingList.remove(); } const contentEditor = document.querySelector('.ttv-content-editor'); contentEditor.parentNode.insertBefore(chapterList, contentEditor); // Hiển thị thông báo về số chương tìm thấy showNotification(`Đã tìm thấy ${chapters.length} chương và tự động điền vào form.`); // Tự động chọn chương đầu tiên if (chapters.length > 0) { selectChapter(chapters[0], 0); } } // Đếm số từ và ký tự function updateWordCount(content) { const wordCount = content.trim().split(/\s+/).length; const charCount = content.length; return `${wordCount} từ | ${charCount} ký tự`; } // Chuyển đổi giữa chế độ soạn thảo và xem trước function togglePreview() { const contentEditor = document.querySelector('.ttv-content-editor'); const preview = document.querySelector('.ttv-preview'); const previewBtn = document.querySelector('.ttv-preview-btn'); if (contentEditor.style.display !== 'none') { contentEditor.style.display = 'none'; preview.style.display = 'block'; preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>'); previewBtn.textContent = 'Soạn thảo'; } else { contentEditor.style.display = 'block'; preview.style.display = 'none'; previewBtn.textContent = 'Xem trước'; } } // Chuyển đổi chế độ toàn màn hình function toggleFullscreen() { const panel = document.querySelector('.ttv-control-panel'); const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn'); panel.classList.toggle('fullscreen'); fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình'; } // Thêm panel điều khiển function addControlPanel() { // Tạo panel const panel = document.createElement('div'); panel.className = 'ttv-control-panel'; // Thêm header const header = document.createElement('div'); header.className = 'ttv-header'; header.innerHTML = ` <div>Soạn Thảo Nội Dung</div> <div class="ttv-toolbar"> <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button> <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button> <button class="ttv-minimize">−</button> </div> `; const buttonGroup = document.createElement('div'); buttonGroup.className = 'ttv-button-group'; // Khung soạn thảo nội dung const contentEditorLabel = document.createElement('div'); contentEditorLabel.className = 'ttv-heading'; contentEditorLabel.innerHTML = ` Nội dung chương: <span class="ttv-word-count">0 từ | 0 ký tự</span> `; const contentEditor = document.createElement('textarea'); contentEditor.className = 'ttv-content-editor'; contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...'; // Khung xem trước const preview = document.createElement('div'); preview.className = 'ttv-preview'; // Cập nhật số từ khi nhập nội dung contentEditor.oninput = () => { const wordCountSpan = document.querySelector('.ttv-word-count'); if (wordCountSpan) { wordCountSpan.textContent = updateWordCount(contentEditor.value); } }; // Xử lý khi paste nội dung contentEditor.onpaste = (e) => { // Cho phép paste hoàn tất setTimeout(() => { const content = contentEditor.value; const chapters = parseChapters(content); if (chapters.length > 0) { displayChapters(chapters); // Tự động điền form sau khi hiển thị danh sách chương setTimeout(() => { try { transferContent(); } catch (error) { console.error('Lỗi khi tự động điền form:', error); showNotification('Có lỗi xảy ra khi tự động điền form!', true); } }, 1000); // Đợi 1s để đảm bảo UI đã được cập nhật hoàn toàn } }, 0); }; // Nút chuyển nội dung const transferBtn = document.createElement('button'); transferBtn.type = 'button'; transferBtn.className = 'btn btn-warning'; transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>'; transferBtn.onclick = transferContent; // Thêm các phần tử vào panel panel.appendChild(header); buttonGroup.appendChild(contentEditorLabel); buttonGroup.appendChild(contentEditor); buttonGroup.appendChild(preview); buttonGroup.appendChild(transferBtn); panel.appendChild(buttonGroup); document.body.appendChild(panel); // Thêm xử lý sự kiện cho các nút trong toolbar const minimizeBtn = panel.querySelector('.ttv-minimize'); minimizeBtn.onclick = () => { panel.classList.toggle('minimized'); minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−'; }; window.togglePreview = togglePreview; window.toggleFullscreen = toggleFullscreen; } // Initialize chapter values when page loads window.addEventListener('load', function() { dăngnhanhTTV.initializeChapterValues(); addControlPanel(); }); })();