您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu
当前为
// ==UserScript== // @name TTV // @namespace http://tampermonkey.net/ // @version 2.2 // @description Công cụ đăng chương hiện đại cho Tàng Thư Viện với UI/UX được tối ưu // @author HA // @match https://tangthuvien.net/dang-chuong/story/* // @match https://tangthuvien.net/danh-sach-chuong/story/* // @match http://localhost:5000/* // @match http://127.0.0.1:5000/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @required https://code.jquery.com/jquery-3.2.1.min.js // ==/UserScript== (function() { 'use strict'; // Polyfills for GM_* functions if (typeof GM_addStyle === 'undefined') { function GM_addStyle(css) { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } window.GM_addStyle = GM_addStyle; } if (typeof GM_setValue === 'undefined') { function GM_setValue(key, value) { localStorage.setItem(key, JSON.stringify(value)); } window.GM_setValue = GM_setValue; } if (typeof GM_getValue === 'undefined') { function GM_getValue(key, defaultValue) { const value = localStorage.getItem(key); return value === null ? defaultValue : JSON.parse(value); } window.GM_getValue = GM_getValue; } // Constants const HEADER_SIGN = ""; const FOOTER_SIGN = ""; const MAX_CHAPTER_POST = 10; GM_addStyle(` #modern-uploader { background-color: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-bottom: 20px; position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 320px; z-index: 1000; } .button-container { display: flex; justify-content: space-between; align-items: center; gap: 15px; margin-top: 15px; } .btn { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 500; transition: all 0.2s ease; } .btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .btn-success { background-color: #34a853; color: white; border: none; } .btn-success:hover { background-color: #2d9748; } .btn-danger { background-color: #ea4335; color: white; border: none; margin-left: auto; } .btn-danger:hover { background-color: #d33426; } .form-control { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; margin-bottom: 10px; font-size: 14px; transition: border-color 0.2s ease; } .form-control:focus { border-color: #4285f4; outline: none; } .chapter-character-count { text-align: right; font-size: 12px; margin-top: 5px; color: #666; } `); function showNotification(message, type) { const notification = jQuery("<div>", { class: `notification-${type}`, text: message, css: { position: "fixed", top: "20px", left: "50%", transform: "translateX(-50%)", backgroundColor: type === "success" ? "#4CAF50" : type === "warning" ? "#fbbc05" : "#ea4335", color: "white", padding: "12px 20px", borderRadius: "8px", zIndex: "10000", fontSize: "14px", fontWeight: "500", boxShadow: "0 4px 10px rgba(0,0,0,0.15)" } }); jQuery("body").append(notification); notification.fadeIn(300).delay(3000).fadeOut(500, function() { jQuery(this).remove(); }); } function createChapterHTML(chapNum) { const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1; const chap_vol_name = jQuery('.chap_vol_name').val() || ''; return ` <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK"> <div class="col-xs-12 form-group"></div> <div class="form-group"> <label class="col-sm-2" for="chap_stt">STT</label> <div class="col-sm-8"> <input class="form-control" required name="chap_stt[${chapNum}]" value="${dăngnhanhTTV.STATE.CHAP_STT}" placeholder="Số thứ tự của chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_number">Chương thứ..</label> <div class="col-sm-8"> <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Quyển số</label> <div class="col-sm-8"> <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên quyển</label> <div class="col-sm-8"> <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" /> </div> </div> <div class="form-group"> <label class="col-sm-2" for="chap_name">Tên chương</label> <div class="col-sm-8"> <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/> </div> </div> <div class="form-group"> <label class="col-sm-2" for="introduce">Nội dung</label> <div class="col-sm-8"> <textarea maxlength="75000" style="color:#000;font-weight: 400;" required class="form-control" name="introduce[${chapNum}]" rows="20" placeholder="Nội dung" type="text"></textarea> <div class="chapter-character-count"></div> </div> </div> <div class="form-group"> <label class="col-sm-2" for="adv">Quảng cáo</label> <div class="col-sm-8"> <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea> </div> </div> </div>`; } function setupCharacterCounter() { jQuery(document).on("input", "[name^=introduce]", function() { const text = jQuery(this).val(); const charCount = text.length; let charCountElement = jQuery(this).next('.chapter-character-count'); if (charCountElement.length === 0) { charCountElement = jQuery('<div class="chapter-character-count"></div>'); jQuery(this).after(charCountElement); } if(charCount < 3000) { charCountElement.html(`<span style="color: #ff0000;">${charCount.toLocaleString()}/3.000 ký tự</span>`); } else if(charCount > 20000) { charCountElement.html(`<span style="color: #fbbc05;">${charCount.toLocaleString()}/20.000 ký tự</span>`); } else { charCountElement.html(`<span style="color: #34a853;">${charCount.toLocaleString()}/20.000 ký tự</span>`); } }); } const dăngnhanhTTV = { STATE: { CHAP_NUMBER: 1, CHAP_STT: 1, CHAP_SERIAL: 1, CHAP_NUMBER_ORIGINAL: 1, CHAP_STT_ORIGINAL: 1, CHAP_SERIAL_ORIGINAL: 1 }, ELEMENTS: { qpContent: null, qpButtonSubmit: null, qpButtonRemoveEmpty: null }, init: function() { try { console.log('TTV Uploader đang khởi tạo...'); this.initializeChapterValues(); this.createInterface(); this.cacheElements(); this.registerEvents(); console.log('TTV Uploader đã khởi tạo xong'); showNotification('TTV Uploader đã khởi tạo thành công', 'success'); } catch (e) { console.error('Lỗi khởi tạo:', e); showNotification('Có lỗi khi khởi tạo TTV Uploader', 'error'); } }, createInterface: function() { const html = ` <div id="modern-uploader"> <div class="text-center mb-4"> <h3 style="color: #4285f4; margin-bottom: 15px; font-weight: 600;">📝 CÔNG CỤ ĐĂNG NHANH</h3> </div> <div class="form-group"> <textarea placeholder="Nội dung truyện (Dán vào đây để tự động tách chương)" id="qpContent" class="form-control" rows="5"></textarea> </div> <div class="button-container"> <button class="btn btn-success" id="qpButtonSubmit">📤 Đăng chương</button> <button class="btn btn-danger" id="qpButtonRemoveEmpty" style="display: none;">Ẩn chương trống</button> </div> </div>`; jQuery(".list-in-user").before(html); }, initializeChapterValues: function() { try { const chap_number = parseInt(jQuery('#chap_number').val()); let chap_stt = parseInt(jQuery('.chap_stt1').val()); let chap_serial = parseInt(jQuery('.chap_serial').val()); if (parseInt(jQuery('#chap_stt').val()) > chap_stt) { chap_stt = parseInt(jQuery('#chap_stt').val()); } if (parseInt(jQuery('#chap_serial').val()) > chap_serial) { chap_serial = parseInt(jQuery('#chap_serial').val()); } this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1; this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1; this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1; } catch (e) { console.error("Error initializing chapter values:", e); } }, cacheElements: function() { this.ELEMENTS.qpContent = jQuery("#qpContent"); this.ELEMENTS.qpButtonSubmit = jQuery("#qpButtonSubmit"); this.ELEMENTS.qpButtonRemoveEmpty = jQuery("#qpButtonRemoveEmpty"); }, registerEvents: function() { this.ELEMENTS.qpContent.on("paste", this.handlePaste.bind(this)); this.ELEMENTS.qpButtonRemoveEmpty.on('click', this.removeEmptyChapters.bind(this)); this.ELEMENTS.qpButtonSubmit.on('click', this.submitChapters.bind(this)); setupCharacterCounter(); }, handlePaste: function(e) { e.preventDefault(); this.ELEMENTS.qpContent.val(""); this.showLoading(); const pastedText = e.originalEvent.clipboardData.getData('text'); this.ELEMENTS.qpContent.val(pastedText); setTimeout(this.performAction.bind(this), 100); }, performAction: function() { try { console.log("Starting performAction"); var text = this.ELEMENTS.qpContent.val(); if (!text) { showNotification('Không có nội dung để tách chương', 'error'); this.hideLoading(); return 0; } var debugOutput = []; var chapters = []; var lines = text.split('\n'); var currentChapter = []; var lastTitle = null; debugOutput.push("=== Processing Text ==="); debugOutput.push(`Total lines: ${lines.length}`); debugOutput.push("=== Line Analysis ==="); // Helper function to show exact whitespace function visualizeWhitespace(str) { return str.split('').map(c => { if (c === '\t') return '\\t'; if (c === ' ') return '·'; if (c === '\n') return '\\n'; return c; }).join(''); } for (let i = 0; i < lines.length; i++) { let line = lines[i]; let isChapterTitle = /^\t[Cc]hương\s*\d+\s*:/.test(line) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(line); // Debug log with exact whitespace debugOutput.push(`Line ${i}: ${visualizeWhitespace(line.substring(0, 50))}${line.length > 50 ? '...' : ''}`); debugOutput.push(` Is chapter: ${isChapterTitle}`); if (isChapterTitle) { if (currentChapter.length > 0) { if (line !== lastTitle) { chapters.push(currentChapter.join('\n')); currentChapter = [line]; lastTitle = line; debugOutput.push(` -> New chapter started`); } else { debugOutput.push(` -> Skipped duplicate title`); } } else { currentChapter = [line]; lastTitle = line; debugOutput.push(` -> First chapter started`); } } else if (currentChapter.length > 0) { currentChapter.push(line); } } if (currentChapter.length > 0) { chapters.push(currentChapter.join('\n')); debugOutput.push("-> Added final chapter"); } debugOutput.push("=== Results ==="); debugOutput.push(`Found ${chapters.length} chapters`); // Phân tách chương dài const processedChapters = []; for (let i = 0; i < chapters.length; i++) { const chapterLines = chapters[i].split('\n'); const title = chapterLines.shift().trim(); const chapterText = chapterLines.join('\n'); // Đếm số ký tự của chương const charCount = chapterText.length; debugOutput.push(`Chapter ${i+1} character count: ${charCount}`); // Nếu chương có kích thước lớn hơn 20000 ký tự, chia nhỏ if (charCount > 20000) { // Số chương cần tách const parts = Math.ceil(charCount / 20000); debugOutput.push(`Splitting into ${parts} parts`); // Tính số ký tự cho mỗi phần const charsPerPart = Math.ceil(charCount / parts); debugOutput.push(`Characters per part: ~${charsPerPart}`); // Chia chương thành các phần đều nhau dựa trên số ký tự let currentText = chapterText; let totalProcessed = 0; for (let part = 0; part < parts; part++) { // Xác định kích thước phần này const isLastPart = part === parts - 1; const targetSize = isLastPart ? currentText.length : charsPerPart; // Tìm điểm kết thúc phù hợp (kết thúc câu hoặc đoạn văn) let endPos = Math.min(targetSize, currentText.length); // Tìm điểm kết thúc là cuối đoạn văn hoặc cuối câu gần với vị trí mục tiêu if (!isLastPart && endPos < currentText.length) { // Tìm vị trí xuống dòng gần nhất const nextParagraph = currentText.indexOf('\n\n', endPos - 500); if (nextParagraph !== -1 && nextParagraph < endPos + 500) { endPos = nextParagraph + 2; // +2 để bao gồm cả ký tự xuống dòng } else { // Nếu không tìm thấy đoạn văn, tìm dấu kết thúc câu const sentenceEnd = Math.max( currentText.lastIndexOf('. ', endPos), currentText.lastIndexOf('! ', endPos), currentText.lastIndexOf('? ', endPos) ); if (sentenceEnd !== -1 && sentenceEnd > endPos - 500) { endPos = sentenceEnd + 2; // +2 để bao gồm dấu kết thúc câu và khoảng trắng } } } // Lấy nội dung của phần này const partContent = currentText.substring(0, endPos); totalProcessed += partContent.length; // Cập nhật text còn lại cho phần tiếp theo currentText = currentText.substring(endPos); // Tạo tiêu đề phần: chỉ lấy tiêu đề gốc và thêm phần chia let chapterTitle = title; if (title.includes(':')) { chapterTitle = title.substring(title.indexOf(':') + 1).trim(); } // Thêm thông tin phần vào tiêu đề gốc let newTitle = `${title} (Phần ${part+1}/${parts})`; processedChapters.push(newTitle + '\n' + partContent); debugOutput.push(`Part ${part+1}: ${partContent.length} chars`); } debugOutput.push(`Total processed: ${totalProcessed}/${charCount} chars`); } else { // Giữ nguyên chương nếu không cần chia processedChapters.push(chapters[i]); } } debugOutput.push(`After processing: ${processedChapters.length} chapters`); // Giới hạn chỉ lấy 10 chương đầu tiên vào form, phần còn lại sẽ copy vào clipboard const chaptersToFill = processedChapters.slice(0, MAX_CHAPTER_POST); const remainingChapters = processedChapters.slice(MAX_CHAPTER_POST); // Fill chapters into forms var titles = jQuery("input[name^='chap_name']"); var contents = jQuery("textarea[name^='introduce']"); var advs = jQuery("textarea[name^='adv']"); debugOutput.push(`Forms found: ${titles.length}`); if (processedChapters.length === 0) { showNotification('Không tìm thấy chương nào', 'error'); jQuery('#debug-output').text(debugOutput.join('\n')); return; } // Nếu có chương còn lại, sao chép vào clipboard if (remainingChapters.length > 0) { debugOutput.push(`${remainingChapters.length} chapters will be copied to clipboard`); } // Tạo thêm form nếu số chương cần điền nhiều hơn số form hiện có const neededForms = chaptersToFill.length - titles.length; if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) { debugOutput.push(`Need to add ${neededForms} more forms`); for (let i = 0; i < neededForms && (titles.length + i) < MAX_CHAPTER_POST; i++) { this.addNewChapter(); } // Cập nhật lại danh sách các form sau khi thêm titles = jQuery("input[name^='chap_name']"); contents = jQuery("textarea[name^='introduce']"); advs = jQuery("textarea[name^='adv']"); } debugOutput.push(`Filling ${chaptersToFill.length} chapters into forms`); // Điền nội dung vào form jQuery.each(titles, function(k, v) { if (k < chaptersToFill.length) { var content = chaptersToFill[k].split('\n'); var title = content.shift().trim(); //Preserve the "Chương X:" prefix var originalTitle = title; debugOutput.push(`\nFilling chapter ${k + 1}:`); debugOutput.push(`Original title: ${title}`); debugOutput.push(`Extracted title: ${originalTitle}`); debugOutput.push(`Content length: ${content.length} lines`); titles[k].value = originalTitle; contents[k].value = HEADER_SIGN + "\r\n" + content.join('\n') + "\r\n" + FOOTER_SIGN; if (advs[k]) advs[k].value = ""; // Trigger character counter update jQuery(contents[k]).trigger('input'); } }); // Sao chép các chương còn lại vào clipboard nếu có if (remainingChapters.length > 0) { try { // Tạo nội dung clipboard với định dạng rõ ràng để dễ phân biệt các chương const clipboardContent = remainingChapters.map(chap => chap.trim()).join('\n\n---CHAPTER_SEPARATOR---\n\n'); // Thử trước với Clipboard API (hiện đại) if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(clipboardContent) .then(() => { debugOutput.push(`Đã sao chép ${remainingChapters.length} chương vào clipboard (Clipboard API)`); showNotification(`Đã sao chép ${remainingChapters.length} chương vào clipboard`, 'success'); }) .catch(err => { throw err; // Chuyển đến phương pháp dự phòng }); } else { // Phương pháp dự phòng với execCommand (cũ) const tempTextarea = document.createElement('textarea'); tempTextarea.style.position = 'fixed'; tempTextarea.style.top = '0'; tempTextarea.style.left = '0'; tempTextarea.style.width = '2em'; tempTextarea.style.height = '2em'; tempTextarea.style.opacity = '0'; tempTextarea.style.pointerEvents = 'none'; tempTextarea.value = clipboardContent; document.body.appendChild(tempTextarea); tempTextarea.focus(); tempTextarea.select(); const successful = document.execCommand('copy'); document.body.removeChild(tempTextarea); if (!successful) { throw new Error('Không thể sao chép vào clipboard'); } debugOutput.push(`Đã sao chép ${remainingChapters.length} chương vào clipboard (execCommand)`); showNotification(`Đã sao chép ${remainingChapters.length} chương vào clipboard`, 'success'); } } catch (err) { console.error('Lỗi khi sao chép vào clipboard:', err); debugOutput.push(`Lỗi khi sao chép vào clipboard: ${err.message}`); showNotification('Không thể sao chép vào clipboard. Vui lòng thử lại.', 'error'); // Hiển thị textarea để copy thủ công nếu tự động không thành công const manualCopyArea = document.createElement('div'); manualCopyArea.style.position = 'fixed'; manualCopyArea.style.top = '50%'; manualCopyArea.style.left = '50%'; manualCopyArea.style.transform = 'translate(-50%, -50%)'; manualCopyArea.style.backgroundColor = 'white'; manualCopyArea.style.padding = '20px'; manualCopyArea.style.borderRadius = '8px'; manualCopyArea.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; manualCopyArea.style.zIndex = '10000'; manualCopyArea.style.maxWidth = '80%'; manualCopyArea.style.maxHeight = '80%'; manualCopyArea.style.overflow = 'auto'; manualCopyArea.innerHTML = ` <h3 style="margin-top: 0;">Sao chép thủ công</h3> <p>Không thể sao chép tự động. Vui lòng chọn toàn bộ nội dung bên dưới và sao chép (Ctrl+C):</p> <textarea style="width: 100%; height: 300px; padding: 10px;">${clipboardContent}</textarea> <div style="text-align: right; margin-top: 10px;"> <button id="closeManualCopy" style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">Đóng</button> </div> `; document.body.appendChild(manualCopyArea); document.getElementById('closeManualCopy').addEventListener('click', () => { document.body.removeChild(manualCopyArea); }); } } this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success"); this.ELEMENTS.qpContent.val("Đã thực hiện xong"); // Hiển thị thông báo sau khi xử lý xong if (remainingChapters.length > 0) { let message = `Đã sử lý ${chapters.length} chương\n`; message += `Đã nhập ${MAX_CHAPTER_POST} chương\n`; message += `Đã coppy ${remainingChapters.length} chương`; // Đếm số chương được chia let splitChapters = 0; for (let i = 0; i < chapters.length; i++) { const chapterText = chapters[i].split('\n').slice(1).join('\n'); if (chapterText.length > 20000) { splitChapters++; } } if (processedChapters.length > chapters.length) { message += `\nCó ${splitChapters} chương chia thành ${processedChapters.length - (chapters.length - splitChapters)} chương`; } showNotification(message, 'success'); } else if (processedChapters.length > chapters.length) { // Đếm số chương được chia let splitChapters = 0; for (let i = 0; i < chapters.length; i++) { const chapterText = chapters[i].split('\n').slice(1).join('\n'); if (chapterText.length > 20000) { splitChapters++; } } let message = `Đã sử lý ${chapters.length} chương\n`; message += `Đã nhập ${processedChapters.length} chương\n`; message += `Có ${splitChapters} chương chia thành ${processedChapters.length - (chapters.length - splitChapters)} chương`; showNotification(message, 'success'); } else { showNotification(`Đã sử lý ${chapters.length} chương\nĐã nhập ${processedChapters.length} chương vào form`, 'success'); } // Show debug output jQuery('#debug-output').text(debugOutput.join('\n')); return processedChapters.length; } catch (e) { console.error("Error in performAction:", e); showNotification('Có lỗi khi tách chương', 'error'); return 0; } }, removeEmptyChapters: function() { const forms = document.querySelectorAll('[data-gen="MK_GEN"]'); let removed = 0; forms.forEach(form => { const content = form.querySelector('textarea[name^="introduce"]').value.trim(); if (!content) { form.remove(); removed++; this.updateChapNumber(false); } }); showNotification(`Đã xử lý ${forms.length} chương`, 'info'); }, submitChapters: function() { const forms = document.querySelectorAll('[data-gen="MK_GEN"]'); let hasError = false; forms.forEach(form => { const contentTextarea = form.querySelector('textarea[name^="introduce"]'); const content = contentTextarea.value; if (content.length < 3000) { // Đánh dấu form với màu đỏ rõ ràng hơn contentTextarea.style.border = '3px solid #ff0000'; contentTextarea.style.backgroundColor = 'rgba(255, 0, 0, 0.08)'; // Đổi màu chữ để dễ đọc hơn trên nền đỏ contentTextarea.style.color = '#cc0000'; // Thêm đường viền ngoài cho parent để làm nổi bật toàn bộ form contentTextarea.parentNode.style.border = '1px solid #ff0000'; contentTextarea.parentNode.style.padding = '5px'; contentTextarea.parentNode.style.borderRadius = '5px'; contentTextarea.parentNode.style.backgroundColor = 'rgba(255, 0, 0, 0.03)'; hasError = true; // Thêm thông báo lỗi bên dưới textarea nổi bật hơn let errorMsg = form.querySelector('.chapter-length-error'); if (!errorMsg) { errorMsg = document.createElement('div'); errorMsg.className = 'chapter-length-error'; errorMsg.style.color = '#ff0000'; errorMsg.style.marginTop = '5px'; errorMsg.style.fontSize = '14px'; errorMsg.style.fontWeight = 'bold'; errorMsg.style.padding = '5px 10px'; errorMsg.style.backgroundColor = '#fff0f0'; errorMsg.style.border = '1px solid #ff0000'; errorMsg.style.borderRadius = '3px'; contentTextarea.parentNode.appendChild(errorMsg); } errorMsg.textContent = `⚠️ Chương quá ngắn: ${content.length}/3000 ký tự cần thiết`; } else { // Reset tất cả các style khi chương đủ dài contentTextarea.style.border = ''; contentTextarea.style.backgroundColor = ''; contentTextarea.style.color = ''; contentTextarea.parentNode.style.border = ''; contentTextarea.parentNode.style.padding = ''; contentTextarea.parentNode.style.borderRadius = ''; contentTextarea.parentNode.style.backgroundColor = ''; // Xóa thông báo lỗi nếu có const errorMsg = form.querySelector('.chapter-length-error'); if (errorMsg) { errorMsg.remove(); } } }); if (hasError) { showNotification('Có chương ngắn hơn 3000 ký tự, vui lòng kiểm tra các ô màu đỏ', 'error'); return; } this.showLoading(); // Submit form gốc document.querySelector('form[name="postChapForm"] button[type="submit"]').click(); setTimeout(() => this.hideLoading(), 2000); }, addNewChapter: function() { if ((this.STATE.CHAP_NUMBER + 1) <= MAX_CHAPTER_POST) { this.updateChapNumber(true); const html = createChapterHTML(this.STATE.CHAP_NUMBER); jQuery('#add-chap').before(html); setupCharacterCounter(); } else { showNotification(`Chỉ có thể đăng tối đa ${MAX_CHAPTER_POST} chương một lần`, 'warning'); } }, updateChapNumber: function(isAdd) { try { if (isAdd) { let maxStt = 0; let maxSerial = 0; // Tìm giá trị lớn nhất trong tất cả các input hiện có jQuery('input[name^="chap_stt"]').each(function() { const val = parseInt(jQuery(this).val()) || 0; maxStt = Math.max(maxStt, val); }); jQuery('input[name^="chap_number"]').each(function() { const val = parseInt(jQuery(this).val()) || 0; maxSerial = Math.max(maxSerial, val); }); // So sánh với giá trị từ DOM const chapStt = parseInt(jQuery('.chap_stt1').val()) || 0; const chapSerial = parseInt(jQuery('.chap_serial').val()) || 0; maxStt = Math.max(maxStt, chapStt); maxSerial = Math.max(maxSerial, chapSerial); // Cập nhật biến đếm this.STATE.CHAP_STT = maxStt + 1; this.STATE.CHAP_SERIAL = maxSerial + 1; this.STATE.CHAP_NUMBER++; } else { if (this.STATE.CHAP_NUMBER > this.STATE.CHAP_NUMBER_ORIGINAL) { this.STATE.CHAP_NUMBER--; } if (this.STATE.CHAP_STT > this.STATE.CHAP_STT_ORIGINAL) { this.STATE.CHAP_STT--; } if (this.STATE.CHAP_SERIAL > this.STATE.CHAP_SERIAL_ORIGINAL) { this.STATE.CHAP_SERIAL--; } } jQuery('#chap_number').val(this.STATE.CHAP_NUMBER); jQuery('#chap_stt').val(this.STATE.CHAP_STT); jQuery('#chap_serial').val(this.STATE.CHAP_SERIAL); jQuery('#countNumberPost').text(this.STATE.CHAP_NUMBER); } catch (e) { console.log("Lỗi: " + e); } }, showLoading: function() { const loading = jQuery("<div>", { class: "loading-overlay", css: { position: "fixed", inset: 0, backgroundColor: "rgba(0, 0, 0, 0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, html: "<div class='loading-spinner'></div>" }); jQuery("body").append(loading); }, hideLoading: function() { jQuery(".loading-overlay").remove(); } }; // Initialize app dăngnhanhTTV.init(); })();