TTV Auto Upload

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

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

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

(function() {
    'use strict';

    // CSS tối giản
    const style = document.createElement('style');
    style.textContent = `
        .ttv-notification {position:fixed;top:20px;right:20px;padding:10px 20px;background:#4CAF50;color:white;border-radius:4px;z-index:9999;display:none}
        .ttv-error {background:#f44336}
        .ttv-control-panel {position:fixed;top:50px;right:20px;background:white;padding:25px;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.15);z-index:9998;width:500px;transition:all 0.3s ease}
        .ttv-control-panel.minimized {width:auto;height:auto;padding:10px;opacity:0.8;transform:translateX(calc(100% - 40px))}
        .ttv-control-panel.minimized:hover {opacity:1;transform:translateX(0)}
        .ttv-control-panel.minimized .ttv-button-group,.ttv-control-panel.minimized .ttv-header {display:none}
        .ttv-button-group {display:flex;flex-direction:column;gap:15px}
        .ttv-content-editor {width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;resize:vertical}
        .ttv-preview {display:none;width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;background:#f9f9f9}
        .ttv-heading {font-size:15px;color:#555;margin:12px 0;display:flex;justify-content:space-between;align-items:center}
        .ttv-word-count {font-size:12px;color:#888;padding:3px 8px;background:#f5f5f5;border-radius:4px}
        .ttv-chapter-list {width:100%;margin:10px 0;max-height:200px;overflow-y:auto;border:1px solid #eee;border-radius:6px;padding:8px}
        .ttv-chapter-item {padding:8px;border-bottom:1px solid #eee;cursor:pointer;font-size:12px;color:#666}
        .ttv-chapter-item .chapter-title {font-weight:bold;margin-bottom:4px}
        .ttv-chapter-item .chapter-name {color:#888;padding-left:10px;border-left:2px solid #ddd;margin:4px 0}
        .ttv-chapter-item .chapter-stats {font-size:11px;color:#999}
        .ttv-chapter-item:last-child {border-bottom:none}
        .ttv-chapter-item.selected {background:#f0f8ff;border-left:2px solid #5bc0de}
        button.btn-warning {background:#f0ad4e;color:white;border:none;padding:12px;border-radius:6px;width:100%;font-size:14px}
        button.btn-warning:hover {background:#ec971f}
        button.ttv-minimize {padding:2px 8px;background:none;border:none;cursor:pointer;font-size:16px;color:#666}
    `;
    document.head.appendChild(style);

    // State management
    const dăngnhanhTTV = {
        STATE: {
            CHAP_NUMBER: 1,
            CHAP_STT: 1,
            CHAP_SERIAL: 1,
            CHAP_NUMBER_ORIGINAL: 1,
            CHAP_STT_ORIGINAL: 1,
            CHAP_SERIAL_ORIGINAL: 1
        },
        initializeChapterValues: function() {
            try {
                const chap_number = parseInt(jQuery('#chap_number').val());
                let chap_stt = parseInt(jQuery('.chap_stt1').val());
                let chap_serial = parseInt(jQuery('.chap_serial').val());

                if (parseInt(jQuery('#chap_stt').val()) > chap_stt) {
                    chap_stt = parseInt(jQuery('#chap_stt').val());
                }
                if (parseInt(jQuery('#chap_serial').val()) > chap_serial) {
                    chap_serial = parseInt(jQuery('#chap_serial').val());
                }

                this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1;
                this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1;
                this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1;
            } catch (e) {
                console.error("Error initializing chapter values:", e);
            }
        }
    };

    // UI Elements
    const notification = document.createElement('div');
    notification.className = 'ttv-notification';
    document.body.appendChild(notification);

    // Hiển thị thông báo
    function showNotification(message, isError = false) {
        notification.textContent = message;
        notification.className = 'ttv-notification' + (isError ? ' ttv-error' : '');
        notification.style.display = 'block';
        setTimeout(() => notification.style.display = 'none', 3000);
    }

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

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

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

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

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

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

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

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

            setTimeout(() => {
                try {
                    transferContent();
                } catch (error) {
                    console.error('Lỗi khi tự động điền form:', error);
                    showNotification('Có lỗi xảy ra khi tự động điền form!', true);
                }
            }, 1000);
        }

        return chapters;
    }

    // Tạo HTML cho form chương
    function createChapterHTML(chapNum) {
        const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
        const chap_vol_name = jQuery('.chap_vol_name').val() || '';
        return `
        <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK">
            <div class="col-xs-12 form-group"></div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_stt">STT</label>
                <div class="col-sm-8">
                    <input class="form-control" required name="chap_stt[${chapNum}]" value="${dăngnhanhTTV.STATE.CHAP_STT}" placeholder="Số thứ tự của chương" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_number">Chương thứ..</label>
                <div class="col-sm-8">
                    <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Quyển số</label>
                <div class="col-sm-8">
                    <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Tên quyển</label>
                <div class="col-sm-8">
                    <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" />
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="chap_name">Tên chương</label>
                <div class="col-sm-8">
                    <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="introduce">Nội dung</label>
                <div class="col-sm-8">
                    <textarea maxlength="75000" style="color:#000;font-weight: 400;" required class="form-control" name="introduce[${chapNum}]" rows="20" placeholder="Nội dung" type="text"></textarea>
                    <div class="chapter-character-count"></div>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2" for="adv">Quảng cáo</label>
                <div class="col-sm-8">
                    <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea>
                </div>
            </div>
        </div>`;
    }

    // Chuyển nội dung vào form
    async function transferContent() {
        try {
            const chapterItems = document.querySelectorAll('.ttv-chapter-item');
            if (!chapterItems.length) {
                throw new Error('Không tìm thấy chương nào để điền vào form');
            }

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

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

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

            let maxChapStt = 0;
            let maxChapSerial = 0;

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

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

                    const sttInput = formElem.querySelector(`input[name="chap_stt[${formIndex}]"]`);
                    if (sttInput && sttInput.value && !isNaN(parseInt(sttInput.value))) {
                        const sttVal = parseInt(sttInput.value);
                        if (sttVal > maxChapStt) {
                            maxChapStt = sttVal;
                        }
                    }

                    const serialInput = formElem.querySelector(`input[name="chap_number[${formIndex}]"]`);
                    if (serialInput && serialInput.value && !isNaN(parseInt(serialInput.value))) {
                        const serialVal = parseInt(serialInput.value);
                        if (serialVal > maxChapSerial) {
                            maxChapSerial = serialVal;
                        }
                    }
                }
            });

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

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

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

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

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

                const chapterHTML = createChapterHTML(formIndex);
                const tempDiv = document.createElement('div');
                tempDiv.innerHTML = chapterHTML;
                const newFormElement = tempDiv.firstElementChild;

                if (!newFormElement) {
                    throw new Error(`Không thể tạo element form cho chương ${formIndex}`);
                }

                form.appendChild(newFormElement);

                const chapterItem = chapterItems[i];
                const titleElement = chapterItem.querySelector('.chapter-title');
                const nameElement = chapterItem.querySelector('.chapter-name');

                if (!titleElement || !nameElement) {
                    throw new Error(`Thiếu thông tin tiêu đề hoặc tên cho chương ${formIndex}`);
                }

                const chapterTitle = titleElement.textContent;
                const chapterName = nameElement.textContent.replace('Tên chương: ', '');

                const formFields = {
                    chapterName: form.querySelector(`input[name="chap_name[${formIndex}]"]`),
                    content: form.querySelector(`textarea[name="introduce[${formIndex}]"]`),
                    chapterNumber: form.querySelector(`input[name="chap_number[${formIndex}]"]`),
                    chapterOrder: form.querySelector(`input[name="chap_stt[${formIndex}]"]`),
                    volume: form.querySelector(`input[name="vol[${formIndex}]"]`),
                    volumeName: form.querySelector(`input[name="vol_name[${formIndex}]"]`),
                    advertisement: form.querySelector(`textarea[name="adv[${formIndex}]"]`)
                };

                formFields.chapterName.value = chapterName;
                formFields.chapterNumber.value = dăngnhanhTTV.STATE.CHAP_SERIAL.toString();
                formFields.chapterOrder.value = dăngnhanhTTV.STATE.CHAP_STT.toString();
                formFields.volume.value = chap_vol;
                formFields.volumeName.value = chap_vol_name;

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

                formFields.advertisement.value = '';

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

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

        } catch (error) {
            console.error('Lỗi khi điền form:', error);
            showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true);
        }
    }

    // Hiển thị danh sách chương
    function displayChapters(chapters) {
        const chapterList = document.createElement('div');
        chapterList.className = 'ttv-chapter-list';

        chapters.forEach((chapter, index) => {
            const chapterItem = document.createElement('div');
            chapterItem.className = 'ttv-chapter-item';
            // Chỉ lấy phần nội dung dưới tiêu đề, không bao gồm tiêu đề
            chapterItem._content = chapter.content.join('\n');

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

            const titleDiv = document.createElement('div');
            titleDiv.className = 'chapter-title';
            titleDiv.textContent = chapter.title;

            const nameDiv = document.createElement('div');
            nameDiv.className = 'chapter-name';
            nameDiv.textContent = `Tên chương: ${chapter.name}`;

            const statsDiv = document.createElement('div');
            statsDiv.className = 'chapter-stats';
            statsDiv.textContent = `Chương ${chapterNum} | ${chapter.content.length} dòng`;

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

            if (index === 0) {
                chapterItem.classList.add('selected');
            }
        });

        const existingList = document.querySelector('.ttv-chapter-list');
        if (existingList) {
            existingList.remove();
        }

        const contentEditor = document.querySelector('.ttv-content-editor');
        contentEditor.parentNode.insertBefore(chapterList, contentEditor);

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

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

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

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

        try {
            transferContent();
        } catch (error) {
            console.error('Lỗi khi tự động điền form:', error);
            showNotification('Có lỗi xảy ra khi tự động điền form!', true);
        }
    }

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

    // Xem trước
    function togglePreview() {
        const contentEditor = document.querySelector('.ttv-content-editor');
        const preview = document.querySelector('.ttv-preview');
        const previewBtn = document.querySelector('.ttv-preview-btn');

        if (contentEditor.style.display !== 'none') {
            contentEditor.style.display = 'none';
            preview.style.display = 'block';
            preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>');
            previewBtn.textContent = 'Soạn thảo';
        } else {
            contentEditor.style.display = 'block';
            preview.style.display = 'none';
            previewBtn.textContent = 'Xem trước';
        }
    }

    // Toàn màn hình
    function toggleFullscreen() {
        const panel = document.querySelector('.ttv-control-panel');
        const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn');
        panel.classList.toggle('fullscreen');
        fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình';
    }

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

        const header = document.createElement('div');
        header.className = 'ttv-header';
        header.innerHTML = `
            <div>Soạn Thảo Nội Dung</div>
            <div class="ttv-toolbar">
                <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button>
                <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button>
                <button class="ttv-minimize">−</button>
            </div>
        `;

        const buttonGroup = document.createElement('div');
        buttonGroup.className = 'ttv-button-group';

        const contentEditorLabel = document.createElement('div');
        contentEditorLabel.className = 'ttv-heading';
        contentEditorLabel.innerHTML = `
            Nội dung chương:
            <span class="ttv-word-count">0 từ | 0 ký tự</span>
        `;

        const contentEditor = document.createElement('textarea');
        contentEditor.className = 'ttv-content-editor';
        contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...';

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

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

        contentEditor.onpaste = (e) => {
            setTimeout(() => {
                const content = contentEditor.value;
                const chapters = parseChapters(content);
                if (chapters.length > 0) {
                    displayChapters(chapters);
                    setTimeout(() => {
                        try {
                            transferContent();
                        } catch (error) {
                            console.error('Lỗi khi tự động điền form:', error);
                            showNotification('Có lỗi xảy ra khi tự động điền form!', true);
                        }
                    }, 1000);
                }
            }, 0);
        };

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

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

        document.body.appendChild(panel);

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

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

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