TTV

Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu

目前为 2025-03-07 提交的版本。查看 最新版本

// ==UserScript==
// @name         TTV
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu
// @author       HA
// @match        https://tangthuvien.net/dang-chuong/story/*
// @match        https://tangthuvien.net/danh-sach-chuong/story/*
// @match        http://localhost:5000/*
// @match        http://127.0.0.1:5000/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @required     https://code.jquery.com/jquery-3.2.1.min.js
// ==/UserScript==

(function() {
    'use strict';

    // Polyfills for GM_* functions
    if (typeof GM_addStyle === 'undefined') {
        function GM_addStyle(css) {
            const style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);
        }
        window.GM_addStyle = GM_addStyle;
    }

    if (typeof GM_setValue === 'undefined') {
        function GM_setValue(key, value) {
            localStorage.setItem(key, JSON.stringify(value));
        }
        window.GM_setValue = GM_setValue;
    }

    if (typeof GM_getValue === 'undefined') {
        function GM_getValue(key, defaultValue) {
            const value = localStorage.getItem(key);
            return value === null ? defaultValue : JSON.parse(value);
        }
        window.GM_getValue = GM_getValue;
    }

    // Constants
    const HEADER_SIGN = "";
    const FOOTER_SIGN = "";
    const MAX_CHAPTER_POST = 10;

    GM_addStyle(`
        #modern-uploader {
            background-color: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;
            position: fixed;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            width: 320px;
            z-index: 1000;
        }
        .button-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 15px;
            margin-top: 15px;
        }
        .btn {
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 500;
            transition: all 0.2s ease;
        }
        .btn:hover {
            transform: translateY(-1px);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .btn-success {
            background-color: #34a853;
            color: white;
            border: none;
        }
        .btn-success:hover {
            background-color: #2d9748;
        }
        .btn-danger {
            background-color: #ea4335;
            color: white;
            border: none;
            margin-left: auto;
        }
        .btn-danger:hover {
            background-color: #d33426;
        }
        .form-control {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            margin-bottom: 10px;
            font-size: 14px;
            transition: border-color 0.2s ease;
        }
        .form-control:focus {
            border-color: #4285f4;
            outline: none;
        }
        .chapter-character-count {
            text-align: right;
            font-size: 12px;
            margin-top: 5px;
            color: #666;
        }
    `);

    function showNotification(message, type) {
        const notification = jQuery("<div>", {
            class: `notification-${type}`,
            text: message,
            css: {
                position: "fixed",
                top: "20px",
                left: "50%",
                transform: "translateX(-50%)",
                backgroundColor: type === "success" ? "#4CAF50" : type === "warning" ? "#fbbc05" : "#ea4335",
                color: "white",
                padding: "12px 20px",
                borderRadius: "8px",
                zIndex: "10000",
                fontSize: "14px",
                fontWeight: "500",
                boxShadow: "0 4px 10px rgba(0,0,0,0.15)"
            }
        });
        jQuery("body").append(notification);
        notification.fadeIn(300).delay(3000).fadeOut(500, function() {
            jQuery(this).remove();
        });
    }

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

    function setupCharacterCounter() {
        jQuery(document).on("input", "[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) {
                charCountElement.html(`<span style="color: #ff0000;">${charCount.toLocaleString()}/3.000 ký tự</span>`);
            } else if(charCount > 20000) {
                charCountElement.html(`<span style="color: #fbbc05;">${charCount.toLocaleString()}/20.000 ký tự</span>`);
            } else {
                charCountElement.html(`<span style="color: #34a853;">${charCount.toLocaleString()}/20.000 ký tự</span>`);
            }
        });
    }

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

        ELEMENTS: {
            qpContent: null,
            qpButtonSubmit: null,
            qpButtonRemoveEmpty: null
        },

        init: function() {
            try {
                console.log('TTV Uploader đang khởi tạo...');
                this.initializeChapterValues();
                this.createInterface();
                this.cacheElements();
                this.registerEvents();
                console.log('TTV Uploader đã khởi tạo xong');
                showNotification('TTV Uploader đã khởi tạo thành công', 'success');
            } catch (e) {
                console.error('Lỗi khởi tạo:', e);
                showNotification('Có lỗi khi khởi tạo TTV Uploader', 'error');
            }
        },

        createInterface: function() {
            const html = `
            <div id="modern-uploader">
                <div class="text-center mb-4">
                    <h3 style="color: #4285f4; margin-bottom: 15px; font-weight: 600;">📝 CÔNG CỤ ĐĂNG NHANH</h3>
                </div>
                <div class="form-group">
                    <textarea placeholder="Nội dung truyện (Dán vào đây để tự động tách chương)" id="qpContent" class="form-control" rows="5"></textarea>
                </div>
                <div class="button-container">
                    <button class="btn btn-success" id="qpButtonSubmit">📤 Đăng chương</button>
                    <button class="btn btn-danger" id="qpButtonRemoveEmpty" style="display: none;">Ẩn chương trống</button>
                </div>
            </div>`;

            jQuery(".list-in-user").before(html);
        },

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

        cacheElements: function() {
            this.ELEMENTS.qpContent = jQuery("#qpContent");
            this.ELEMENTS.qpButtonSubmit = jQuery("#qpButtonSubmit");
            this.ELEMENTS.qpButtonRemoveEmpty = jQuery("#qpButtonRemoveEmpty");
        },

        registerEvents: function() {
            this.ELEMENTS.qpContent.on("paste", this.handlePaste.bind(this));
            this.ELEMENTS.qpButtonRemoveEmpty.on('click', this.removeEmptyChapters.bind(this));
            this.ELEMENTS.qpButtonSubmit.on('click', this.submitChapters.bind(this));
            setupCharacterCounter();
        },

        handlePaste: function(e) {
            e.preventDefault();
            this.ELEMENTS.qpContent.val("");
            this.showLoading();

            const pastedText = e.originalEvent.clipboardData.getData('text');
            this.ELEMENTS.qpContent.val(pastedText);

            setTimeout(this.performAction.bind(this), 100);
        },

        performAction: function() {
            try {
                console.log("Starting performAction");
                var text = this.ELEMENTS.qpContent.val();

                if (!text) {
                    showNotification('Không có nội dung để tách chương', 'error');
                    this.hideLoading();
                    return 0;
                }

                var debugOutput = [];
                var chapters = [];
                var lines = text.split('\n');
                var currentChapter = [];
                var lastTitle = null;

                debugOutput.push("=== Processing Text ===");
                debugOutput.push(`Total lines: ${lines.length}`);
                debugOutput.push("=== Line Analysis ===");

                // Helper function to show exact whitespace
                function visualizeWhitespace(str) {
                    return str.split('').map(c => {
                        if (c === '\t') return '\\t';
                        if (c === ' ') return '·';
                        if (c === '\n') return '\\n';
                        return c;
                    }).join('');
                }

                for (let i = 0; i < lines.length; i++) {
                    let line = lines[i];
                    let isChapterTitle = /^\t[Cc]hương\s*\d+\s*:/.test(line) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(line);

                    // Debug log with exact whitespace
                    debugOutput.push(`Line ${i}: ${visualizeWhitespace(line.substring(0, 50))}${line.length > 50 ? '...' : ''}`);
                    debugOutput.push(`  Is chapter: ${isChapterTitle}`);

                    if (isChapterTitle) {
                        if (currentChapter.length > 0) {
                            if (line !== lastTitle) {
                                chapters.push(currentChapter.join('\n'));
                                currentChapter = [line];
                                lastTitle = line;
                                debugOutput.push(`  -> New chapter started`);
                            } else {
                                debugOutput.push(`  -> Skipped duplicate title`);
                            }
                        } else {
                            currentChapter = [line];
                            lastTitle = line;
                            debugOutput.push(`  -> First chapter started`);
                        }
                    } else if (currentChapter.length > 0) {
                        currentChapter.push(line);
                    }
                }

                if (currentChapter.length > 0) {
                    chapters.push(currentChapter.join('\n'));
                    debugOutput.push("-> Added final chapter");
                }

                debugOutput.push("=== Results ===");
                debugOutput.push(`Found ${chapters.length} chapters`);

                // Phân tách chương dài
                const processedChapters = [];
                for (let i = 0; i < chapters.length; i++) {
                    const chapterLines = chapters[i].split('\n');
                    const title = chapterLines.shift().trim();
                    const chapterText = chapterLines.join('\n');

                    // Đếm số ký tự của chương
                    const charCount = chapterText.length;
                    debugOutput.push(`Chapter ${i+1} character count: ${charCount}`);

                    // Nếu chương có kích thước lớn hơn 20000 ký tự, chia nhỏ
                    if (charCount > 20000) {
                        // Số chương cần tách
                        const parts = Math.ceil(charCount / 20000);
                        debugOutput.push(`Splitting into ${parts} parts`);

                        // Tính số ký tự cho mỗi phần
                        const charsPerPart = Math.ceil(charCount / parts);
                        debugOutput.push(`Characters per part: ~${charsPerPart}`);

                        // Chia chương thành các phần đều nhau dựa trên số ký tự
                        let currentText = chapterText;
                        let totalProcessed = 0;

                        for (let part = 0; part < parts; part++) {
                            // Xác định kích thước phần này
                            const isLastPart = part === parts - 1;
                            const targetSize = isLastPart ? currentText.length : charsPerPart;

                            // Tìm điểm kết thúc phù hợp (kết thúc câu hoặc đoạn văn)
                            let endPos = Math.min(targetSize, currentText.length);

                            // Tìm điểm kết thúc là cuối đoạn văn hoặc cuối câu gần với vị trí mục tiêu
                            if (!isLastPart && endPos < currentText.length) {
                                // Tìm vị trí xuống dòng gần nhất
                                const nextParagraph = currentText.indexOf('\n\n', endPos - 500);
                                if (nextParagraph !== -1 && nextParagraph < endPos + 500) {
                                    endPos = nextParagraph + 2; // +2 để bao gồm cả ký tự xuống dòng
                                } else {
                                    // Nếu không tìm thấy đoạn văn, tìm dấu kết thúc câu
                                    const sentenceEnd = Math.max(
                                        currentText.lastIndexOf('. ', endPos),
                                        currentText.lastIndexOf('! ', endPos),
                                        currentText.lastIndexOf('? ', endPos)
                                    );

                                    if (sentenceEnd !== -1 && sentenceEnd > endPos - 500) {
                                        endPos = sentenceEnd + 2; // +2 để bao gồm dấu kết thúc câu và khoảng trắng
                                    }
                                }
                            }

                            // Lấy nội dung của phần này
                            const partContent = currentText.substring(0, endPos);
                            totalProcessed += partContent.length;

                            // Cập nhật text còn lại cho phần tiếp theo
                            currentText = currentText.substring(endPos);

                            // Tạo tiêu đề phần: chỉ lấy tiêu đề gốc và thêm phần chia
                            let chapterTitle = title;
                            if (title.includes(':')) {
                                chapterTitle = title.substring(title.indexOf(':') + 1).trim();
                            }

                            // Thêm thông tin phần vào tiêu đề gốc
                            let newTitle = `${title} (Phần ${part+1}/${parts})`;
                            processedChapters.push(newTitle + '\n' + partContent);

                            debugOutput.push(`Part ${part+1}: ${partContent.length} chars`);
                        }

                        debugOutput.push(`Total processed: ${totalProcessed}/${charCount} chars`);
                    } else {
                        // Giữ nguyên chương nếu không cần chia
                        processedChapters.push(chapters[i]);
                    }
                }

                debugOutput.push(`After processing: ${processedChapters.length} chapters`);

                // Giới hạn chỉ lấy 10 chương đầu tiên vào form, phần còn lại sẽ copy vào clipboard
                const chaptersToFill = processedChapters.slice(0, MAX_CHAPTER_POST);
                const remainingChapters = processedChapters.slice(MAX_CHAPTER_POST);

                // Fill chapters into forms
                var titles = jQuery("input[name^='chap_name']");
                var contents = jQuery("textarea[name^='introduce']");
                var advs = jQuery("textarea[name^='adv']");

                debugOutput.push(`Forms found: ${titles.length}`);

                if (processedChapters.length === 0) {
                    showNotification('Không tìm thấy chương nào', 'error');
                    jQuery('#debug-output').text(debugOutput.join('\n'));
                    return;
                }

                // Nếu có chương còn lại, sao chép vào clipboard
                if (remainingChapters.length > 0) {
                    debugOutput.push(`${remainingChapters.length} chapters will be copied to clipboard`);
                }

                // Tạo thêm form nếu số chương cần điền nhiều hơn số form hiện có
                const neededForms = chaptersToFill.length - titles.length;
                if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) {
                    debugOutput.push(`Need to add ${neededForms} more forms`);
                    for (let i = 0; i < neededForms && (titles.length + i) < MAX_CHAPTER_POST; i++) {
                        this.addNewChapter();
                    }
                    // Cập nhật lại danh sách các form sau khi thêm
                    titles = jQuery("input[name^='chap_name']");
                    contents = jQuery("textarea[name^='introduce']");
                    advs = jQuery("textarea[name^='adv']");
                }

                debugOutput.push(`Filling ${chaptersToFill.length} chapters into forms`);

                // Điền nội dung vào form
                jQuery.each(titles, function(k, v) {
                    if (k < chaptersToFill.length) {
                        var content = chaptersToFill[k].split('\n');
                        var title = content.shift().trim();
                        //Preserve the "Chương X:" prefix
                        var originalTitle = title;


                        debugOutput.push(`\nFilling chapter ${k + 1}:`);
                        debugOutput.push(`Original title: ${title}`);
                        debugOutput.push(`Extracted title: ${originalTitle}`);
                        debugOutput.push(`Content length: ${content.length} lines`);

                        titles[k].value = originalTitle;
                        contents[k].value = HEADER_SIGN + "\r\n" + content.join('\n') + "\r\n" + FOOTER_SIGN;
                        if (advs[k]) advs[k].value = "";

                        // Trigger character counter update
                        jQuery(contents[k]).trigger('input');
                    }
                });

                // Sao chép các chương còn lại vào clipboard nếu có
                if (remainingChapters.length > 0) {
                    try {
                        // Tạo nội dung clipboard với định dạng rõ ràng để dễ phân biệt các chương
                        const clipboardContent = remainingChapters.map(chap => chap.trim()).join('\n\n---CHAPTER_SEPARATOR---\n\n');

                        // Thử trước với Clipboard API (hiện đại)
                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(clipboardContent)
                                .then(() => {
                                    debugOutput.push(`Đã sao chép ${remainingChapters.length} chương vào clipboard (Clipboard API)`);
                                    showNotification(`Đã sao chép ${remainingChapters.length} chương vào clipboard`, 'success');
                                })
                                .catch(err => {
                                    throw err; // Chuyển đến phương pháp dự phòng
                                });
                        } else {
                            // Phương pháp dự phòng với execCommand (cũ)
                            const tempTextarea = document.createElement('textarea');
                            tempTextarea.style.position = 'fixed';
                            tempTextarea.style.top = '0';
                            tempTextarea.style.left = '0';
                            tempTextarea.style.width = '2em';
                            tempTextarea.style.height = '2em';
                            tempTextarea.style.opacity = '0';
                            tempTextarea.style.pointerEvents = 'none';
                            tempTextarea.value = clipboardContent;

                            document.body.appendChild(tempTextarea);
                            tempTextarea.focus();
                            tempTextarea.select();

                            const successful = document.execCommand('copy');
                            document.body.removeChild(tempTextarea);

                            if (!successful) {
                                throw new Error('Không thể sao chép vào clipboard');
                            }

                            debugOutput.push(`Đã sao chép ${remainingChapters.length} chương vào clipboard (execCommand)`);
                            showNotification(`Đã sao chép ${remainingChapters.length} chương vào clipboard`, 'success');
                        }
                    } catch (err) {
                        console.error('Lỗi khi sao chép vào clipboard:', err);
                        debugOutput.push(`Lỗi khi sao chép vào clipboard: ${err.message}`);
                        showNotification('Không thể sao chép vào clipboard. Vui lòng thử lại.', 'error');

                        // Hiển thị textarea để copy thủ công nếu tự động không thành công
                        const manualCopyArea = document.createElement('div');
                        manualCopyArea.style.position = 'fixed';
                        manualCopyArea.style.top = '50%';
                        manualCopyArea.style.left = '50%';
                        manualCopyArea.style.transform = 'translate(-50%, -50%)';
                        manualCopyArea.style.backgroundColor = 'white';
                        manualCopyArea.style.padding = '20px';
                        manualCopyArea.style.borderRadius = '8px';
                        manualCopyArea.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
                        manualCopyArea.style.zIndex = '10000';
                        manualCopyArea.style.maxWidth = '80%';
                        manualCopyArea.style.maxHeight = '80%';
                        manualCopyArea.style.overflow = 'auto';

                        manualCopyArea.innerHTML = `
                            <h3 style="margin-top: 0;">Sao chép thủ công</h3>
                            <p>Không thể sao chép tự động. Vui lòng chọn toàn bộ nội dung bên dưới và sao chép (Ctrl+C):</p>
                            <textarea style="width: 100%; height: 300px; padding: 10px;">${clipboardContent}</textarea>
                            <div style="text-align: right; margin-top: 10px;">
                                <button id="closeManualCopy" style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">Đóng</button>
                            </div>
                        `;

                        document.body.appendChild(manualCopyArea);

                        document.getElementById('closeManualCopy').addEventListener('click', () => {
                            document.body.removeChild(manualCopyArea);
                        });
                    }
                }

                this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success");
                this.ELEMENTS.qpContent.val("Đã thực hiện xong");

                // Hiển thị thông báo sau khi xử lý xong
                if (remainingChapters.length > 0) {
                    let message = `Đã sử lý ${chapters.length} chương\n`;
                    message += `Đã nhập ${MAX_CHAPTER_POST} chương\n`;
                    message += `Đã coppy ${remainingChapters.length} chương`;

                    // Đếm số chương được chia
                    let splitChapters = 0;
                    for (let i = 0; i < chapters.length; i++) {
                        const chapterText = chapters[i].split('\n').slice(1).join('\n');
                        if (chapterText.length > 20000) {
                            splitChapters++;
                        }
                    }

                    if (processedChapters.length > chapters.length) {
                        message += `\nCó ${splitChapters} chương chia thành ${processedChapters.length - (chapters.length - splitChapters)} chương`;
                    }

                    showNotification(message, 'success');
                } else if (processedChapters.length > chapters.length) {
                    // Đếm số chương được chia
                    let splitChapters = 0;
                    for (let i = 0; i < chapters.length; i++) {
                        const chapterText = chapters[i].split('\n').slice(1).join('\n');
                        if (chapterText.length > 20000) {
                            splitChapters++;
                        }
                    }

                    let message = `Đã sử lý ${chapters.length} chương\n`;
                    message += `Đã nhập ${processedChapters.length} chương\n`;
                    message += `Có ${splitChapters} chương chia thành ${processedChapters.length - (chapters.length - splitChapters)} chương`;
                    showNotification(message, 'success');
                } else {
                    showNotification(`Đã sử lý ${chapters.length} chương\nĐã nhập ${processedChapters.length} chương vào form`, 'success');
                }

                // Show debug output
                jQuery('#debug-output').text(debugOutput.join('\n'));

                return processedChapters.length;
            } catch (e) {
                console.error("Error in performAction:", e);
                showNotification('Có lỗi khi tách chương', 'error');
                return 0;
            }
        },

        removeEmptyChapters: function() {
            const forms = document.querySelectorAll('[data-gen="MK_GEN"]');
            let removed = 0;

            forms.forEach(form => {
                const content = form.querySelector('textarea[name^="introduce"]').value.trim();
                if (!content) {
                    form.remove();
                    removed++;
                    this.updateChapNumber(false);
                }
            });

            showNotification(`Đã xử lý ${forms.length} chương`, 'info');
        },

        submitChapters: function() {
            const forms = document.querySelectorAll('[data-gen="MK_GEN"]');
            let hasError = false;

            forms.forEach(form => {
                const contentTextarea = form.querySelector('textarea[name^="introduce"]');
                const content = contentTextarea.value;
                if (content.length < 3000) {
                    // Đánh dấu form với màu đỏ rõ ràng hơn
                    contentTextarea.style.border = '3px solid #ff0000';
                    contentTextarea.style.backgroundColor = 'rgba(255, 0, 0, 0.08)';
                    // Đổi màu chữ để dễ đọc hơn trên nền đỏ
                    contentTextarea.style.color = '#cc0000';
                    // Thêm đường viền ngoài cho parent để làm nổi bật toàn bộ form
                    contentTextarea.parentNode.style.border = '1px solid #ff0000';
                    contentTextarea.parentNode.style.padding = '5px';
                    contentTextarea.parentNode.style.borderRadius = '5px';
                    contentTextarea.parentNode.style.backgroundColor = 'rgba(255, 0, 0, 0.03)';
                    hasError = true;

                    // Thêm thông báo lỗi bên dưới textarea nổi bật hơn
                    let errorMsg = form.querySelector('.chapter-length-error');
                    if (!errorMsg) {
                        errorMsg = document.createElement('div');
                        errorMsg.className = 'chapter-length-error';
                        errorMsg.style.color = '#ff0000';
                        errorMsg.style.marginTop = '5px';
                        errorMsg.style.fontSize = '14px';
                        errorMsg.style.fontWeight = 'bold';
                        errorMsg.style.padding = '5px 10px';
                        errorMsg.style.backgroundColor = '#fff0f0';
                        errorMsg.style.border = '1px solid #ff0000';
                        errorMsg.style.borderRadius = '3px';
                        contentTextarea.parentNode.appendChild(errorMsg);
                    }
                    errorMsg.textContent = `⚠️ Chương quá ngắn: ${content.length}/3000 ký tự cần thiết`;
                } else {
                    // Reset tất cả các style khi chương đủ dài
                    contentTextarea.style.border = '';
                    contentTextarea.style.backgroundColor = '';
                    contentTextarea.style.color = '';
                    contentTextarea.parentNode.style.border = '';
                    contentTextarea.parentNode.style.padding = '';
                    contentTextarea.parentNode.style.borderRadius = '';
                    contentTextarea.parentNode.style.backgroundColor = '';

                    // Xóa thông báo lỗi nếu có
                    const errorMsg = form.querySelector('.chapter-length-error');
                    if (errorMsg) {
                        errorMsg.remove();
                    }
                }
            });

            if (hasError) {
                showNotification('Có chương ngắn hơn 3000 ký tự, vui lòng kiểm tra các ô màu đỏ', 'error');
                return;
            }

            this.showLoading();
            // Submit form gốc
            document.querySelector('form[name="postChapForm"] button[type="submit"]').click();
            setTimeout(() => this.hideLoading(), 2000);
        },

        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();
            } else {
                showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, 'warning');
            }
        },

        updateChapNumber: function(isAdd) {
            try {
                if (isAdd) {
                    let maxStt = 0;
                    let maxSerial = 0;

                    // Tìm giá trị lớn nhất trong tất cả các input hiện có
                    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);
                    });

                    // So sánh với giá trị từ DOM
                    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);

                    // Cập nhật biến đếm
                    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);
            }
        },

        showLoading: function() {
            const loading = jQuery("<div>", {
                class: "loading-overlay",
                css: {
                    position: "fixed",
                    inset: 0,
                    backgroundColor: "rgba(0, 0, 0, 0.5)",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    zIndex: 9999
                },
                html: "<div class='loading-spinner'></div>"
            });
            jQuery("body").append(loading);
        },

        hideLoading: function() {
            jQuery(".loading-overlay").remove();
        }
    };

    // Initialize app
    dăngnhanhTTV.init();
})();