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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TTV
// @namespace    http://tampermonkey.net/
// @version      4.3
// @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: 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: 350px;
            z-index: 1000;
        }
        .button-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 20px;
            margin-top: 20px;
        }
        .btn {
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: 14px;
            transition: all 0.2s ease;
        }
        .btn:hover {
            transform: translateY(-1px);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        #modern-uploader .btn-primary {
            background-color: #4285f4;
            color: white;
            border: none;
        }
        #modern-uploader .btn-primary:hover {
            background-color: #3b78e7;
        }
        #modern-uploader .btn-success {
            background-color: #34a853;
            color: white;
            border: none;
        }
        #modern-uploader .btn-success:hover {
            background-color: #2d9346;
        }
        #modern-uploader .btn-danger {
            background-color: #ea4335;
            color: white;
            border: none;
        }
        #modern-uploader .btn-danger:hover {
            background-color: #d33426;
        }
        #modern-uploader .btn-disable {
            background-color: #ccc;
            color: #777;
            pointer-events: none;
        }
        .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;
        }
        .form-control:focus {
            border-color: #4285f4;
            outline: none;
        }
        .chapter-character-count {
            text-align: right;
            font-size: 12px;
            margin-top: 5px;
            color: #666;
        }
        .short-chapters-warning {
            color: #ff0000;
            animation: blink 1s infinite;
            font-weight: bold;
        }
        @keyframes blink {
            0% { opacity: 1; }
            50% { opacity: 0.5; }
            100% { opacity: 1; }
        }
    `);

    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 sắc phù hợp với loại thông báo
        const notification = jQuery("<div>", {
            class: `notification-${type}`,
            css: {
                backgroundColor: type === "success" ? "#4CAF50" : type === "warning" ? "#fbbc05" : "#ea4335",
                color: "white",
                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"
            }
        });

        // 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ệu ứng hiển thị và ẩn thông báo
        // 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);
            }

            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-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
                        let message = `Đã xử lý ${processedChapters.length} chương\n`;
                        message += `Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} chương\n`;

                        if (splitChapters > 0) {
                            message += `Có ${splitChapters} chương chia thành ${splittedChaptersCount} chương\n`;
                        }

                        if (shortChapters > 0) {
                            message += `<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(`Đã sao chép ${remainingChapters.length} chương vào clipboard (Clipboard API)`);
                                    showNotification(message, '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 = `Đã xử lý ${processedChapters.length} chương\n`;
                    message += `Đã nhập ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} chương\n`;

                    if (splitChapters > 0) {
                        message += `Có ${splitChapters} chương chia thành ${splittedChaptersCount} chương\n`;
                    }

                    if (shortChapters > 0) {
                        message += `<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() {
            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';
                        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();
})();