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 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TTV
// @namespace    http://tampermonkey.net/
// @version      5.4
// @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/*
// @match        https://*.repl.co/*
// @match        https://*.replit.dev/*
// @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 (remains unchanged)
    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 (remains unchanged)
    const HEADER_SIGN = "";
    const FOOTER_SIGN = "";
    const MAX_CHAPTER_POST = 10;

    GM_addStyle(`
        #modern-uploader {
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
            margin-bottom: 15px;
            position: fixed;
            right: 15px;
            top: 50%;
            transform: translateY(-50%);
            width: 400px;
            max-height: 90vh;
            overflow-y: auto;
            z-index: 1000;
        }
        .button-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 15px;
            margin-top: 15px;
        }
        #modern-uploader .btn {
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: 14px;
            transition: all 0.2s ease;
        }
        #modern-uploader .form-control {
            width: 100%;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 8px;
            margin-bottom: 15px;
            font-size: 16px;
            transition: border-color 0.2s ease;
        }
        #modern-uploader .form-control:focus {
            border-color: #4285f4;
            outline: none;
        }

        /* Hiệu ứng nhấp nháy rõ ràng hơn cho cảnh báo */
        @keyframes blink {
            0% { opacity: 1; }
            50% { opacity: 0.3; }
            100% { opacity: 1; }
        }

        /* Đánh dấu chương ngắn */
        .chapter-form textarea.form-control[name^="introduce"] {
            transition: all 0.3s ease;
        }
        .chapter-form textarea.form-control[name^="introduce"].short-chapter {
            color: #ff0000 !important;
            animation: blink 1s infinite !important;
            border: 2px solid #ff0000 !important;
            background-color: rgba(255, 0, 0, 0.05) !important;
        }

        .short-chapters-warning {
            color: #ff0000 !important;
            animation: blink 1s infinite !important;
            font-weight: bold !important;
            display: inline-block !important;
        }

        .chapter-character-count {
            text-align: right;
            font-size: 12px;
            margin-top: 5px;
            color: #666;
            position: relative;
        }

        /* Warning Icon */
        .warning-icon {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            color: #ff0000;
            animation: blink 1s infinite;
            font-size: 16px;
            font-weight: bold;
        }
    `);

    function showNotification(message, type) {
        // Xóa thông báo cũ nếu có
        jQuery('#modern-uploader .notification-container').remove();

        // Tạo container cho thông báo
        const container = jQuery("<div>", {
            class: "notification-container",
            css: {
                width: "100%",
                padding: "10px 0",
                marginTop: "10px",
                textAlign: "left",
                borderTop: "1px solid rgba(0,0,0,0.1)"
            }
        });

        // Tạo thông báo với màu nền xanh và chữ đen
        const notification = jQuery("<div>", {
            class: `notification-${type}`,
            css: {
                backgroundColor: "#e8f5e9", // Màu xanh lá nhạt
                color: "#000000", // Màu chữ đen
                padding: "10px 15px",
                borderRadius: "8px",
                fontSize: "14px",
                fontWeight: "500",
                boxShadow: "0 4px 10px rgba(0,0,0,0.15)",
                display: "inline-block",
                maxWidth: "90%",
                margin: "0",
                wordBreak: "break-word",
                border: "1px solid #81c784" // Viền xanh đậm hơn một chút
            }
        });

        // Xử lý xuống dòng nếu cần
        const lines = message.split('\n');
        lines.forEach((line, index) => {
            notification.append(jQuery("<div>").html(line)); // Use html() to allow HTML tags
        });

        // Thêm thông báo vào container và container vào giao diện của script
        container.append(notification);
        jQuery("#modern-uploader .button-container").after(container);

        // Hiển thị thông báo nhưng không tự động biến mất
        notification.fadeIn(300);
    }

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

            // Add or remove short-chapter class based on character count
            if(charCount < 3000) {
                jQuery(this).addClass('short-chapter');
                charCountElement.html(`<span class="short-chapters-warning">${charCount.toLocaleString()}/3.000 ký tự</span>`);
            } else {
                jQuery(this).removeClass('short-chapter');
                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>`);
                }
            }
        });
    }

    // Validate chapter lengths
    function validateChapterLengths() {
        let hasError = false;
        jQuery('form[name="postChapForm"] .chapter-detail').each(function() {
            const form = this;
            const contentTextarea = form.querySelector('textarea[name^="introduce"]');
            const content = contentTextarea.value;
            if (content.length < 3000) {
                jQuery(contentTextarea).addClass('short-chapter');

                // Add warning icon if not exists
                let warningIcon = form.querySelector('.warning-icon');
                if (!warningIcon) {
                    warningIcon = document.createElement('div');
                    warningIcon.className = 'warning-icon';
                    warningIcon.innerHTML = '⚠️';
                    contentTextarea.parentNode.appendChild(warningIcon);
                }
                hasError = true;
            } else {
                jQuery(contentTextarea).removeClass('short-chapter');
                const warningIcon = form.querySelector('.warning-icon');
                if (warningIcon) {
                    warningIcon.remove();
                }
            }
        });
        return !hasError;
    }


    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-DEBUG] Script bắt đầu khởi tạo...');
                console.log('[TTV-DEBUG] Phiên bản script: 3.0');
                this.initializeChapterValues();
                console.log('[TTV-DEBUG] Đã khởi tạo giá trị chương');
                this.createInterface();
                console.log('[TTV-DEBUG] Đã tạo giao diện');
                this.cacheElements();
                console.log('[TTV-DEBUG] Đã cache các elements');
                this.registerEvents();
                console.log('[TTV-DEBUG] Đã đăng ký các events');
                console.log('[TTV-DEBUG] Script đã khởi động thành công');
                showNotification('Công cụ đã chạy', 'success');
            } catch (e) {
                console.error('[TTV-ERROR] Lỗi khởi tạo:', e);
                showNotification('Có lỗi khi khởi tạo Script', 'error');
            }
        },

        createInterface: function() {
            const html = `
            <div id="modern-uploader">
                <div class="text-center mb-4">
                    <h3 style="color: #4285f4; margin-bottom: 15px; font-weight: 700; font-size: 18px;">📝 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);

            // Đảm bảo luôn gọi hideLoading sau khi performAction hoàn thành
            setTimeout(() => {
                this.performAction();
                this.hideLoading();
            }, 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');
                    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');

                        // Tạo thông báo thống kê trước khi sao chép clipboard
                        let splitChapters = 0;
                        let shortChapters = 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 (chapterText.length < 3000) {
                                shortChapters++;
                            }
                        }

                        // Số chương sau khi chia
                        const splittedChaptersCount = processedChapters.length - (chapters.length - splitChapters);

                        // Tạo thông báo chi tiết
                        let message = '';
                        message = message.concat(`📝Đã xử lý ${processedChapters.length}\n Chương`);
                        message = message.concat(`📝Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`);

                        if (remainingChapters.length > 0) {
                            message = message.concat(`📋Đã lưu lại ${remainingChapters.length} Chương\n`);
                            }

                        if (splitChapters > 0) {
                            message = message.concat(`📑Có ${splitChapters} Chương đã chia thành ${splittedChaptersCount} Chương\n`);
                        }

                        if (shortChapters > 0) {
                            message = message.concat(`⚠️<span class="short-chapters-warning">Có ${shortChapters} chương dưới 3000 ký tự</span>`);
                        }

                        // Thử trước với Clipboard API (hiện đại)
                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(clipboardContent)
                                .then(() => {
                                    debugOutput.push(`📋Đã lưu lại ${remainingChapters.length} Chương\n`);
                                    showNotification(message, remainingChapters.length > 0 ? 'warning' : '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(message, '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 nếu không có chương nào được sao chép vào clipboard
                if (remainingChapters.length === 0) {
                    // Đếm số chương được chia
                    let splitChapters = 0;
                    let shortChapters = 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 (chapterText.length < 3000) {
                            shortChapters++;
                        }
                    }

                    // Số chương sau khi chia
                    const splittedChaptersCount = processedChapters.length - (chapters.length - splitChapters);

                    // Tạo thông báo
                    let message = '';
                    message = message.concat(`📝 Thống kê xử lý chương:\n`);
                    message = message.concat(`• Tổng số chương: ${processedChapters.length}\n`);
                    message = message.concat(`• Đã nhập vào form: ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} chương\n`);

                    if (splitChapters > 0) {
                        message = message.concat(`• 📑 Có ${splitChapters} chương dài<replit_final_file>
 đã được chia thành ${splittedChaptersCount} chương\n`);
                    }

                    if (shortChapters > 0) {
                        message = message.concat(`• ⚠️ <span class="short-chapters-warning">Có ${shortChapters} chương dưới 3000 ký tự</span>`);
                    }

                    showNotification(message, '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() {
            if (!validateChapterLengths()) {
                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();
})();