TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net

目前為 2025-03-08 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         TTV Auto Upload
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Tự động điền form đăng chương trên tangthuvien.net
// @author       HA
// @match        https://tangthuvien.net/dang-chuong/story/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // CSS tối giản
    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;transition:all 0.3s ease}
        .ttv-control-panel.minimized {width:auto;height:auto;padding:10px;opacity:0.8;transform:translateX(calc(100% - 40px))}
        .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;resize:vertical}
        .ttv-preview {display:none;width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;background:#f9f9f9}
        .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}
        .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;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}
        button.btn-warning {background:#f0ad4e;color:white;border:none;padding:12px;border-radius:6px;width:100%;font-size:14px}
        button.btn-warning:hover {background:#ec971f}
        button.ttv-minimize {padding:2px 8px;background:none;border:none;cursor:pointer;font-size:16px;color:#666}
        .chapter-character-count {text-align:right;font-size:12px;margin-top:5px;color:#666}
        textarea[name^="introduce"].short-chapter {border:2px solid #ff0000 !important;background-color:rgba(255,0,0,0.1) !important}
        @keyframes shortChapterBlink {0%{background-color:rgba(255,0,0,0.1)}50%{background-color:rgba(255,0,0,0.2)}100%{background-color:rgba(255,0,0,0.1)}}
        .short-chapters-warning {color:#ff0000;font-weight:bold;animation:shortChapterBlink 1s infinite}
    `;
    document.head.appendChild(style);
    
    // Constants
    const HEADER_SIGN = "";
    const FOOTER_SIGN = "";
    const MAX_CHAPTER_POST = 10;

    // 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,
            AUTO_POST: false,
            TOTAL_CHAPTERS: 0,
            POSTED_CHAPTERS: 0
        },
        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);
            }
        },
        addNewChapter: function() {
            if ((this.STATE.CHAP_NUMBER + 1) <= MAX_CHAPTER_POST) {
                this.updateChapNumber(true);
                const html = createChapterHTML(this.STATE.CHAP_NUMBER);
                jQuery('#add-chap').before(html);
                setupCharacterCounter();
                showNotification(`Đã thêm chương mới`);
            } else {
                showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, true);
            }
        },
        updateChapNumber: function(isAdd) {
            try {
                if (isAdd) {
                    let maxStt = 0;
                    let maxSerial = 0;
                    jQuery('input[name^="chap_stt"]').each(function() {
                        const val = parseInt(jQuery(this).val()) || 0;
                        maxStt = Math.max(maxStt, val);
                    });
                    jQuery('input[name^="chap_number"]').each(function() {
                        const val = parseInt(jQuery(this).val()) || 0;
                        maxSerial = Math.max(maxSerial, val);
                    });
                    const chapStt = parseInt(jQuery('.chap_stt1').val()) || 0;
                    const chapSerial = parseInt(jQuery('.chap_serial').val()) || 0;
                    maxStt = Math.max(maxStt, chapStt);
                    maxSerial = Math.max(maxSerial, chapSerial);
                    this.STATE.CHAP_STT = maxStt + 1;
                    this.STATE.CHAP_SERIAL = maxSerial + 1;
                    this.STATE.CHAP_NUMBER++;
                } else {
                    if (this.STATE.CHAP_NUMBER > this.STATE.CHAP_NUMBER_ORIGINAL) {
                        this.STATE.CHAP_NUMBER--;
                    }
                    if (this.STATE.CHAP_STT > this.STATE.CHAP_STT_ORIGINAL) {
                        this.STATE.CHAP_STT--;
                    }
                    if (this.STATE.CHAP_SERIAL > this.STATE.CHAP_SERIAL_ORIGINAL) {
                        this.STATE.CHAP_SERIAL--;
                    }
                }
                jQuery('#chap_number').val(this.STATE.CHAP_NUMBER);
                jQuery('#chap_stt').val(this.STATE.CHAP_STT);
                jQuery('#chap_serial').val(this.STATE.CHAP_SERIAL);
                jQuery('#countNumberPost').text(this.STATE.CHAP_NUMBER);
            } catch (e) {
                console.log("Lỗi: " + e);
            }
        }
    };

    // UI Elements
    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);
    }

    // Phân tích chương
    function parseChapters(content) {
        // Giữ nguyên định dạng gốc của nội dung - không sửa đổi dấu cách hoặc xuống dòng
        const lines = content.split('\n');
        const chapters = [];
        let currentChapter = {title: '', name: '', content: []};
        // Hỗ trợ nhiều định dạng tiêu đề chương phổ biến
        const chapterPatterns = [
            /^\s*Chương\s+\d+\s*:/i,     // Chương X:
            /^\t+Chương\s+\d+\s*:/i,     // [Tab]Chương X:
            /^\s{4,}Chương\s+\d+\s*:/i   // [Spaces]Chương X:
        ];

        // Biến để kiểm soát tiêu đề trùng lặp
        let chapterTitles = new Set();
        let chapterNumbers = new Map(); // Lưu số chương và tiêu đề đã gặp
        let duplicateCount = 0;

        // Mảng lưu nội dung của các dòng trống gần nhất
        let emptyLineBuffer = [];
        // Biến để theo dõi tiêu đề chương trước đó
        let previousChapterLine = '';
        let previousChapterNum = 0;
        // Khoảng cách dòng tối thiểu giữa các tiêu đề hợp lệ
        const MIN_LINES_BETWEEN_CHAPTERS = 5;
        // Số dòng đã qua kể từ tiêu đề cuối
        let linesSinceLastTitle = 0;
        // Map để nhóm các tiêu đề theo số chương
        let chapterGroups = new Map();

        // Hàm kiểm tra xem một dòng có phải là tiêu đề chương không
        function isChapterTitle(line) {
            return chapterPatterns.some(pattern => pattern.test(line));
        }

        // Hàm trích xuất số chương từ tiêu đề
        function extractChapterNumber(line) {
            const match = line.match(/Chương\s+(\d+)\s*:/i);
            return match ? parseInt(match[1]) : 0;
        }

        // Hàm trích xuất tên chương từ tiêu đề
        function extractChapterName(line) {
            const parts = line.split(':');
            if (parts.length < 2) return '';
            return parts.slice(1).join(':').replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim();
        }

        // Hàm lấy mã chương dựa vào tiêu đề
        function getChapterCode(title) {
            // Lấy số chương + tên chương, bỏ qua các ký tự đặc biệt
            const match = title.match(/[Cc]hương\s*(\d+)\s*:/);
            if (!match) return title.trim();
            const chapterNum = match[1];
            return `chap_${chapterNum}`;
        }

        // Pass đầu tiên: thu thập tất cả các tiêu đề chương và nhóm theo số chương
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            if (isChapterTitle(line)) {
                const chapterNum = extractChapterNumber(line);
                const chapterName = extractChapterName(line);
                
                if (!chapterGroups.has(chapterNum)) {
                    chapterGroups.set(chapterNum, []);
                }
                
                chapterGroups.get(chapterNum).push({
                    lineIndex: i,
                    title: lines[i], // Giữ nguyên định dạng gốc
                    name: chapterName,
                    length: chapterName.length
                });
            }
        }
        
        // Phân tích để loại bỏ các tiêu đề trùng lặp trong cùng một chương
        for (const [chapterNum, titles] of chapterGroups.entries()) {
            // Nếu chỉ có một tiêu đề cho số chương này, giữ lại
            if (titles.length === 1) {
                chapterNumbers.set(chapterNum, {
                    title: titles[0].title,
                    name: titles[0].name,
                    lineIndex: titles[0].lineIndex
                });
                continue;
            }
            
            // Nếu có nhiều tiêu đề cho cùng một số chương
            // Chọn tiêu đề có tên dài nhất và xác định nhất
            titles.sort((a, b) => b.length - a.length);
            
            // Ưu tiên tiêu đề đầu tiên nếu các tiêu đề có độ dài tương đương
            if (titles[0].length > 0) {
                chapterNumbers.set(chapterNum, {
                    title: titles[0].title,
                    name: titles[0].name,
                    lineIndex: titles[0].lineIndex
                });
                
                // Đánh dấu các tiêu đề còn lại là trùng lặp
                for (let i = 1; i < titles.length; i++) {
                    duplicateCount++;
                }
            }
        }
        
        // Sắp xếp các chương theo thứ tự dòng trong văn bản
        const sortedChapters = Array.from(chapterNumbers.entries())
            .sort((a, b) => a[1].lineIndex - b[1].lineIndex);
        
        // Pass thứ hai: xây dựng nội dung chương từ các tiêu đề đã được xác định
        for (let i = 0; i < sortedChapters.length; i++) {
            const [chapterNum, chapterInfo] = sortedChapters[i];
            const nextChapterIndex = (i < sortedChapters.length - 1) ? 
                sortedChapters[i+1][1].lineIndex : lines.length;
            
            const chapterContent = [];
            // Thu thập nội dung từ sau tiêu đề đến trước tiêu đề tiếp theo
            for (let j = chapterInfo.lineIndex + 1; j < nextChapterIndex; j++) {
                // Loại bỏ các tiêu đề trùng lặp đã được xác định
                const line = lines[j];
                const trimmedLine = line.trim();
                
                if (isChapterTitle(trimmedLine)) {
                    const lineChapterNum = extractChapterNumber(trimmedLine);
                    // Nếu đây là tiêu đề trùng lặp của chương hiện tại, bỏ qua
                    if (lineChapterNum === chapterNum) {
                        continue;
                    }
                }
                
                chapterContent.push(lines[j]);
            }
            
            chapters.push({
                title: chapterInfo.title,
                name: chapterInfo.name,
                content: chapterContent
            });
        }

        if (chapters.length > 0) {
            displayChapters(chapters);
            if (duplicateCount > 0) {
                showNotification(`Đã tìm thấy ${chapters.length} chương và bỏ qua ${duplicateCount} tiêu đề lặp lại.`);
            } else {
                showNotification(`Đã tìm thấy ${chapters.length} chương và tự động điền vào form.`);
            }

            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);
        }

        return chapters;
    }

    // Thiết lập bộ đếm ký tự
    function setupCharacterCounter() {
        jQuery(document).on("input", "textarea[name^='introduce']", function() {
            const text = jQuery(this).val();
            const charCount = text.length;
            let charCountElement = jQuery(this).next('.chapter-character-count');
            
            if (charCountElement.length === 0) {
                charCountElement = jQuery('<div class="chapter-character-count"></div>');
                jQuery(this).after(charCountElement);
            }
            
            if (charCount < 3000) {
                jQuery(this).addClass('short-chapter');
                charCountElement.html(`<span class="short-chapters-warning">${charCount.toLocaleString()}/40.000 ký tự</span>`);
            } else {
                jQuery(this).removeClass('short-chapter');
                if (charCount > 40000) {
                    charCountElement.html(`<span style="color: #fbbc05;">${charCount.toLocaleString()}/40.000 ký tự</span>`);
                } else {
                    charCountElement.html(`<span style="color: #34a853;">${charCount.toLocaleString()}/40.000 ký tự</span>`);
                }
            }
        });
    }

    // Tạo HTML cho form chương
    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>`;
    }

    // Chuyển nội dung vào form
    async function transferContent() {
        try {
            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');
            }

            // Kiểm tra số lượng chương và giới hạn tối đa
            if (chapterItems.length < 10) {
                showNotification(`Chỉ có ${chapterItems.length} chương - ít hơn 10 chương. Vẫn tiếp tục đăng.`, false);
            } else if (chapterItems.length > MAX_CHAPTER_POST) {
                showNotification(`Đã vượt quá giới hạn ${MAX_CHAPTER_POST} chương cho một lần đăng. Chỉ ${MAX_CHAPTER_POST} chương đầu sẽ được đăng.`, true);
            }

            const form = document.querySelector('form[name="postChapForm"]');
            if (!form) {
                throw new Error('Không tìm thấy form đăng chương');
            }

            let chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
            let chap_vol_name = jQuery('.chap_vol_name').val() || '';

            let maxChapStt = 0;
            let maxChapSerial = 0;

            const existingForms = form.querySelectorAll('[id^="COUNT_CHAP_"]');

            existingForms.forEach(formElem => {
                const formIdMatch = formElem.id.match(/COUNT_CHAP_(\d+)_MK/);
                if (formIdMatch && formIdMatch[1]) {
                    const formIndex = parseInt(formIdMatch[1]);

                    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;
                        }
                    }

                    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;
                        }
                    }
                }
            });

            // Đảm bảo STT và số chương tiếp theo form gốc
            if (maxChapStt > 0) {
                dăngnhanhTTV.STATE.CHAP_STT = maxChapStt;
            }

            if (maxChapSerial > 0) {
                dăngnhanhTTV.STATE.CHAP_SERIAL = maxChapSerial;
            }

            const existingFormCount = existingForms.length;
            // Giới hạn số lượng chương được đăng trong một lần
            const chaptersToProcess = Math.min(chapterItems.length, MAX_CHAPTER_POST);

            for (let i = 0; i < chaptersToProcess; i++) {
                const formIndex = existingFormCount + i + 1;

                // Tăng STT và Serial cho mỗi chương mới
                if (i > 0) {
                    dăngnhanhTTV.STATE.CHAP_STT++;
                    dăngnhanhTTV.STATE.CHAP_SERIAL++;
                }

                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);

                const chapterItem = chapterItems[i];
                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 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}]"]`)
                };

                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;

                if (chapterItem._content) {
                    // Chỉ lấy phần nội dung, không lấy phần tiêu đề
                    // Đảm bảo giữ nguyên định dạng gốc của nội dung, không trim
                    formFields.content.value = HEADER_SIGN + "\r\n" + chapterItem._content + "\r\n" + FOOTER_SIGN;
                } else {
                    formFields.content.value = '';
                }

                formFields.advertisement.value = '';

                const inputEvent = new Event('input', { bubbles: true });
                formFields.content.dispatchEvent(inputEvent);
            }

            if (chaptersToProcess < chapterItems.length) {
                showNotification(`Đã tự động điền ${chaptersToProcess}/${chapterItems.length} chương vào form! Chương còn lại sẽ được đăng ở lần sau.`);
            } else {
                showNotification(`Đã tự động điền ${chaptersToProcess} 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);
        }
    }

    // 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';
            // Chỉ lấy phần nội dung dưới tiêu đề, không bao gồm tiêu đề
            chapterItem._content = chapter.content.join('\n');

            // Trích xuất số chương để hiển thị
            const chapterMatch = chapter.title.match(/Chương\s+(\d+):/);
            const chapterNum = chapterMatch ? chapterMatch[1] : '?';

            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 = `Chương ${chapterNum} | ${chapter.content.length} dòng | ${chapter.content.join('\n').length} ký tự`;

            chapterItem.appendChild(titleDiv);
            chapterItem.appendChild(nameDiv);
            chapterItem.appendChild(statsDiv);
            chapterItem.onclick = () => selectChapter(chapter, index);
            chapterList.appendChild(chapterItem);

            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);

        if (chapters.length > 0) {
            selectChapter(chapters[0], 0);
        }
    }

    // Chọn chương
    function selectChapter(chapter, index) {
        const contentEditor = document.querySelector('.ttv-content-editor');
        // Chỉ lấy nội dung, không lấy tiêu đề để tránh lặp lại
        contentEditor.value = chapter.content.join('\n');

        const wordCountSpan = document.querySelector('.ttv-word-count');
        if (wordCountSpan) {
            wordCountSpan.textContent = updateWordCount(contentEditor.value);
        }

        const chapterItems = document.querySelectorAll('.ttv-chapter-item');
        chapterItems.forEach(item => item.classList.remove('selected'));
        chapterItems[index]?.classList.add('selected');

        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);
        }
    }

    // Tính số từ
    function updateWordCount(content) {
        const wordCount = content.trim().split(/\s+/).length;
        const charCount = content.length;
        return `${wordCount} từ | ${charCount} ký tự`;
    }

    // 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';
        }
    }

    // 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';
    }

    // Tạo panel điều khiển
    function addControlPanel() {
        const panel = document.createElement('div');
        panel.className = 'ttv-control-panel';

        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';

        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...';

        const preview = document.createElement('div');
        preview.className = 'ttv-preview';

        contentEditor.oninput = () => {
            const wordCountSpan = document.querySelector('.ttv-word-count');
            if (wordCountSpan) {
                wordCountSpan.textContent = updateWordCount(contentEditor.value);
            }
        };

        contentEditor.onpaste = (e) => {
            setTimeout(() => {
                const content = contentEditor.value;
                const chapters = parseChapters(content);
                if (chapters.length > 0) {
                    displayChapters(chapters);
                    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);
                }
            }, 0);
        };

        const buttonsContainer = document.createElement('div');
        buttonsContainer.style.display = 'flex';
        buttonsContainer.style.gap = '10px';
        buttonsContainer.style.marginTop = '10px';

        const transferBtn = document.createElement('button');
        transferBtn.type = 'button';
        transferBtn.className = 'btn btn-warning';
        transferBtn.style.flex = '1';
        transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>';
        transferBtn.onclick = transferContent;

        const addChapterBtn = document.createElement('button');
        addChapterBtn.type = 'button';
        addChapterBtn.className = 'btn btn-primary';
        addChapterBtn.style.flex = '1';
        addChapterBtn.innerHTML = '<span>Thêm chương mới</span>';
        addChapterBtn.onclick = () => dăngnhanhTTV.addNewChapter();

        buttonsContainer.appendChild(transferBtn);
        buttonsContainer.appendChild(addChapterBtn);

        panel.appendChild(header);
        buttonGroup.appendChild(contentEditorLabel);
        buttonGroup.appendChild(contentEditor);
        buttonGroup.appendChild(preview);
        buttonGroup.appendChild(buttonsContainer);
        panel.appendChild(buttonGroup);

        document.body.appendChild(panel);

        const minimizeBtn = panel.querySelector('.ttv-minimize');
        minimizeBtn.onclick = () => {
            panel.classList.toggle('minimized');
            minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−';
        };

        window.togglePreview = togglePreview;
        window.toggleFullscreen = toggleFullscreen;
    }

    window.addEventListener('load', function() {
        dăngnhanhTTV.initializeChapterValues();
        addControlPanel();
        setupCharacterCounter();
    });
})();