TTV Auto Upload

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

当前为 2025-03-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name TTV Auto Upload
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Tự động điền form đăng chương trên tangthuvien.net
  6. // @author HA
  7. // @match https://tangthuvien.net/dang-chuong/story/*
  8. // @match https://tangthuvien.net/danh-sach-chuong/story/*
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @required https://code.jquery.com/jquery-3.2.1.min.js
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. if (window.location.href.includes('/danh-sach-chuong/story/')) {
  18. const storyId = window.location.pathname.split('/').pop();
  19. setTimeout(() => {
  20. window.location.href = `https://tangthuvien.net/dang-chuong/story/${storyId}`;
  21. }, 3000);
  22. return;
  23. }
  24.  
  25. const HEADER_SIGN = "";
  26. const FOOTER_SIGN = "";
  27. const MAX_CHAPTER_POST = 10;
  28.  
  29. // CSS tối giản và cải tiến
  30. GM_addStyle(`
  31. #modern-uploader {
  32. background-color: white;
  33. padding: 20px;
  34. border-radius: 10px;
  35. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  36. position: fixed;
  37. right: 20px;
  38. top: 50%;
  39. transform: translateY(-50%);
  40. width: 400px;
  41. max-height: 90vh;
  42. overflow-y: auto;
  43. z-index: 1000;
  44. }
  45. .button-container {
  46. display: flex;
  47. justify-content: space-between;
  48. align-items: center;
  49. gap: 15px;
  50. margin-top: 15px;
  51. }
  52. #modern-uploader .btn {
  53. padding: 10px 20px;
  54. border-radius: 6px;
  55. cursor: pointer;
  56. font-weight: 600;
  57. font-size: 14px;
  58. transition: all 0.2s ease;
  59. }
  60. #modern-uploader .form-control {
  61. width: 100%;
  62. padding: 15px;
  63. border: 1px solid #ddd;
  64. border-radius: 8px;
  65. margin-bottom: 15px;
  66. font-size: 16px;
  67. transition: border-color 0.2s ease;
  68. }
  69. #modern-uploader .form-control:focus {
  70. border-color: #4285f4;
  71. outline: none;
  72. }
  73. .loading-overlay {
  74. position: fixed;
  75. inset: 0;
  76. background-color: rgba(0, 0, 0, 0.5);
  77. display: flex;
  78. align-items: center;
  79. justify-content: center;
  80. z-index: 9999;
  81. }
  82. .loading-spinner {
  83. width: 50px;
  84. height: 50px;
  85. border: 5px solid rgba(255, 255, 255, 0.3);
  86. border-radius: 50%;
  87. border-top-color: white;
  88. animation: spin 1s ease-in-out infinite;
  89. }
  90. @keyframes spin {
  91. to { transform: rotate(360deg); }
  92. }
  93. `);
  94.  
  95. // Hiển thị thông báo
  96. function showNotification(message, type = 'success') {
  97. jQuery('#modern-uploader .notification-container').remove();
  98. const container = jQuery("<div>", {
  99. class: "notification-container",
  100. style: "width: 100%; padding: 10px 0; margin-top: 10px; text-align: left; border-top: 1px solid rgba(0,0,0,0.1);"
  101. });
  102. const notification = jQuery("<div>", {
  103. class: `notification-${type}`,
  104. style: "padding: 10px 15px; border-radius: 8px; font-size: 14px; font-weight: 500; box-shadow: 0 4px 10px rgba(0,0,0,0.15); display: inline-block; max-width: 90%; margin: 0; word-break: break-word;"
  105. });
  106. if (type === 'success') {
  107. notification.css({
  108. "background-color": "#e8f5e9",
  109. "color": "#000000",
  110. "border": "1px solid #81c784"
  111. });
  112. } else if (type === 'error') {
  113. notification.css({
  114. "background-color": "#ffebee",
  115. "color": "#000000",
  116. "border": "1px solid #e57373"
  117. });
  118. } else if (type === 'warning') {
  119. notification.css({
  120. "background-color": "#fff8e1",
  121. "color": "#000000",
  122. "border": "1px solid #ffd54f"
  123. });
  124. } else if (type === 'info') {
  125. notification.css({
  126. "background-color": "#e3f2fd",
  127. "color": "#000000",
  128. "border": "1px solid #64b5f6"
  129. });
  130. }
  131. const lines = message.split('\n');
  132. lines.forEach((line, index) => {
  133. notification.append(jQuery("<div>").html(line));
  134. });
  135. container.append(notification);
  136. jQuery("#modern-uploader .button-container").after(container);
  137. notification.fadeIn(300);
  138. }
  139.  
  140. // State management
  141. const dăngnhanhTTV = {
  142. STATE: {
  143. CHAP_NUMBER: 1,
  144. CHAP_STT: 1,
  145. CHAP_SERIAL: 1,
  146. CHAP_NUMBER_ORIGINAL: 1,
  147. CHAP_STT_ORIGINAL: 1,
  148. CHAP_SERIAL_ORIGINAL: 1
  149. },
  150. ELEMENTS: {
  151. qpContent: null,
  152. qpButtonSubmit: null,
  153. qpButtonRemoveEmpty: null,
  154. qpButtonPaste: null
  155. },
  156.  
  157. init: function() {
  158. try {
  159. console.log('[TTV-DEBUG] Script bắt đầu khởi tạo...');
  160. this.initializeChapterValues();
  161. console.log('[TTV-DEBUG] Đã khởi tạo giá trị chương');
  162.  
  163. this.createInterface();
  164. console.log('[TTV-DEBUG] Đã tạo giao diện');
  165. this.cacheElements();
  166. console.log('[TTV-DEBUG] Đã cache các elements');
  167. this.registerEvents();
  168. console.log('[TTV-DEBUG] Đã đăng ký các events');
  169. console.log('[TTV-DEBUG] Script đã khởi động thành công');
  170. showNotification('Công cụ đã chạy', 'success');
  171. } catch (e) {
  172. console.error('[TTV-ERROR] Lỗi khởi tạo:', e);
  173. showNotification('Có lỗi khi khởi tạo Script', 'error');
  174. }
  175. },
  176.  
  177. initializeChapterValues: function() {
  178. try {
  179. const chap_number = parseInt(jQuery('#chap_number').val());
  180. let chap_stt = parseInt(jQuery('.chap_stt1').val());
  181. let chap_serial = parseInt(jQuery('.chap_serial').val());
  182.  
  183. if (parseInt(jQuery('#chap_stt').val()) > chap_stt) {
  184. chap_stt = parseInt(jQuery('#chap_stt').val());
  185. }
  186. if (parseInt(jQuery('#chap_serial').val()) > chap_serial) {
  187. chap_serial = parseInt(jQuery('#chap_serial').val());
  188. }
  189.  
  190. this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1;
  191. this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1;
  192. this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1;
  193. } catch (e) {
  194. console.error("Error initializing chapter values:", e);
  195. }
  196. },
  197.  
  198. createInterface: function() {
  199. const html = `
  200. <div id="modern-uploader">
  201. <div class="text-center mb-4">
  202. <h3 style="color: #4285f4; margin-bottom: 15px; font-weight: 700; font-size: 18px;">📝 CÔNG C ĐĂNG NHANH</h3>
  203. </div>
  204. <div class="form-group">
  205. <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>
  206. </div>
  207. <div class="button-container" style="display: flex; justify-content: center; gap: 15px;">
  208. <button class="btn btn-primary" id="qpButtonPaste">📋 Paste</button>
  209. <button class="btn btn-success" id="qpButtonSubmit">📤 Đăng chương</button>
  210. <button class="btn btn-danger" id="qpButtonRemoveEmpty">Ẩn chương trng</button>
  211. </div>
  212. <div class="notification-container"></div>
  213. </div>`;
  214.  
  215. jQuery(".list-in-user").before(html);
  216. },
  217.  
  218. cacheElements: function() {
  219. this.ELEMENTS.qpContent = jQuery("#qpContent");
  220. this.ELEMENTS.qpButtonSubmit = jQuery("#qpButtonSubmit");
  221. this.ELEMENTS.qpButtonRemoveEmpty = jQuery("#qpButtonRemoveEmpty");
  222. this.ELEMENTS.qpButtonPaste = jQuery("#qpButtonPaste");
  223. },
  224.  
  225. registerEvents: function() {
  226. this.ELEMENTS.qpContent.on("paste", this.handlePaste.bind(this));
  227. this.ELEMENTS.qpButtonRemoveEmpty.on('click', this.removeEmptyChapters.bind(this));
  228. this.ELEMENTS.qpButtonSubmit.on('click', this.submitChapters.bind(this));
  229. this.ELEMENTS.qpButtonPaste.on('click', this.handlePasteButton.bind(this));
  230. },
  231.  
  232. handlePasteButton: function() {
  233. this.showLoading();
  234. navigator.clipboard.readText()
  235. .then(text => {
  236. this.ELEMENTS.qpContent.val(text);
  237. setTimeout(() => {
  238. this.parseChapters();
  239. this.hideLoading();
  240. }, 100);
  241. })
  242. .catch(err => {
  243. console.error('Không thể đọc dữ liệu từ clipboard:', err);
  244. this.hideLoading();
  245. showNotification('Không thể truy cập clipboard. Vui lòng dán trực tiếp vào ô nội dung.', 'error');
  246. });
  247. },
  248.  
  249. handlePaste: function(e) {
  250. e.preventDefault();
  251. this.ELEMENTS.qpContent.val("");
  252. this.showLoading();
  253. const pastedText = e.originalEvent.clipboardData.getData('text');
  254. this.ELEMENTS.qpContent.val(pastedText);
  255. setTimeout(() => {
  256. this.parseChapters();
  257. this.hideLoading();
  258. }, 100);
  259. },
  260.  
  261. parseChapters: function() {
  262. try {
  263. console.log("Starting parseChapters");
  264. var text = this.ELEMENTS.qpContent.val();
  265.  
  266. if (!text) {
  267. showNotification('Không có nội dung để tách chương', 'error');
  268. return 0;
  269. }
  270.  
  271. var chapters = [];
  272. var lines = text.split('\n');
  273. var currentChapter = [];
  274. var lastTitle = null;
  275.  
  276. // Hàm lấy mã chương dựa vào tiêu đề
  277. function getChapterCode(title) {
  278. // Lấy số chương + tên chương, bỏ qua các ký tự đặc biệt
  279. const match = title.match(/[Cc]hương\s*(\d+)\s*:/);
  280. if (!match) return title.trim();
  281. const chapterNum = match[1];
  282. return `chap_${chapterNum}`;
  283. }
  284.  
  285. for (let i = 0; i < lines.length; i++) {
  286. let line = lines[i];
  287. let isChapterTitle = /^\t[Cc]hương\s*\d+\s*:/.test(line) || /^\s{4,}[Cc]hương\s*\d+\s*:/.test(line);
  288.  
  289. if (isChapterTitle) {
  290. // Lấy mã chương để so sánh
  291. const currentChapterCode = getChapterCode(line);
  292. const lastTitleCode = lastTitle ? getChapterCode(lastTitle) : null;
  293.  
  294. if (currentChapter.length > 0) {
  295. // Kiểm tra nếu chương hiện tại khác chương trước đó
  296. if (currentChapterCode !== lastTitleCode) {
  297. chapters.push(currentChapter.join('\n'));
  298. currentChapter = [line];
  299. lastTitle = line;
  300. console.log(`New chapter started: ${currentChapterCode}`);
  301. } else {
  302. console.log(`Skipped duplicate chapter: ${currentChapterCode}`);
  303. // Không cần thêm dòng này vào chapter hiện tại vì nó là tiêu đề trùng lặp
  304. }
  305. } else {
  306. currentChapter = [line];
  307. lastTitle = line;
  308. console.log(`First chapter started: ${currentChapterCode}`);
  309. }
  310. } else if (currentChapter.length > 0) {
  311. currentChapter.push(line);
  312. }
  313. }
  314.  
  315. if (currentChapter.length > 0) {
  316. chapters.push(currentChapter.join('\n'));
  317. console.log("Added final chapter");
  318. }
  319.  
  320. console.log(`Found ${chapters.length} chapters`);
  321.  
  322. const processedChapters = this.processChapters(chapters);
  323. const chaptersToFill = processedChapters.slice(0, MAX_CHAPTER_POST);
  324. const remainingChapters = processedChapters.slice(MAX_CHAPTER_POST);
  325.  
  326. var titles = jQuery("input[name^='chap_name']");
  327. var contents = jQuery("textarea[name^='introduce']");
  328. var advs = jQuery("textarea[name^='adv']");
  329.  
  330. if (processedChapters.length === 0) {
  331. showNotification('Không tìm thấy chương nào', 'error');
  332. return 0;
  333. }
  334.  
  335. const neededForms = chaptersToFill.length - titles.length;
  336. if (neededForms > 0 && titles.length < MAX_CHAPTER_POST) {
  337. console.log(`Need to add ${neededForms} more forms`);
  338. for (let i = 0; i < neededForms && (titles.length + i) < MAX_CHAPTER_POST; i++) {
  339. this.addNewChapter();
  340. }
  341. titles = jQuery("input[name^='chap_name']");
  342. contents = jQuery("textarea[name^='introduce']");
  343. advs = jQuery("textarea[name^='adv']");
  344. }
  345.  
  346. console.log(`Filling ${chaptersToFill.length} chapters into forms`);
  347. jQuery.each(titles, (k, v) => {
  348. if (k < chaptersToFill.length) {
  349. var content = chaptersToFill[k].split('\n');
  350. var title = content.shift().trim();
  351. var chapterTitle = title;
  352.  
  353. if (title.includes(':')) {
  354. chapterTitle = title.substring(title.indexOf(':') + 1).trim();
  355. }
  356.  
  357. console.log(`Filling chapter ${k + 1}: ${chapterTitle}`);
  358.  
  359. if (!chapterTitle || chapterTitle.trim() === '') {
  360. chapterTitle = "Vô đề";
  361. }
  362.  
  363. titles[k].value = chapterTitle;
  364. contents[k].value = HEADER_SIGN + "\r\n" + content.join('\n') + "\r\n" + FOOTER_SIGN;
  365. if (advs[k]) advs[k].value = "";
  366. }
  367. });
  368.  
  369. if (remainingChapters.length > 0) {
  370. this.handleRemainingChapters(remainingChapters, chapters);
  371. } else {
  372. this.showChaptersReport(chapters, processedChapters);
  373. }
  374.  
  375. this.ELEMENTS.qpButtonSubmit.removeClass("btn-disable").addClass("btn-success");
  376. this.ELEMENTS.qpContent.val("Đã xử lý xong");
  377.  
  378. return processedChapters.length;
  379. } catch (e) {
  380. console.error("Error in parseChapters:", e);
  381. showNotification('Có lỗi khi tách chương: ' + e.message, 'error');
  382. return 0;
  383. }
  384. },
  385.  
  386. processChapters: function(chapters) {
  387. const processedChapters = [];
  388.  
  389. for (let i = 0; i < chapters.length; i++) {
  390. const chapterLines = chapters[i].split('\n');
  391. const title = chapterLines.shift().trim();
  392. const chapterText = chapterLines.join('\n');
  393. const charCount = chapterText.length;
  394.  
  395. console.log(`Chapter ${i+1} character count: ${charCount}`);
  396.  
  397. if (charCount > 40000) {
  398. const parts = Math.ceil(charCount / 40000);
  399. console.log(`Splitting into ${parts} parts`);
  400. const charsPerPart = Math.ceil(charCount / parts);
  401. console.log(`Characters per part: ~${charsPerPart}`);
  402.  
  403. let currentText = chapterText;
  404. let totalProcessed = 0;
  405.  
  406. for (let part = 0; part < parts; part++) {
  407. const isLastPart = part === parts - 1;
  408. const targetSize = isLastPart ? currentText.length : charsPerPart;
  409. let endPos = Math.min(targetSize, currentText.length);
  410.  
  411. if (!isLastPart && endPos < currentText.length) {
  412. const nextParagraph = currentText.indexOf('\n\n', endPos - 500);
  413. if (nextParagraph !== -1 && nextParagraph < endPos + 500) {
  414. endPos = nextParagraph + 2;
  415. } else {
  416. const sentenceEnd = Math.max(
  417. currentText.lastIndexOf('. ', endPos),
  418. currentText.lastIndexOf('! ', endPos),
  419. currentText.lastIndexOf('? ', endPos)
  420. );
  421. if (sentenceEnd !== -1 && sentenceEnd > endPos - 500) {
  422. endPos = sentenceEnd + 2;
  423. }
  424. }
  425. }
  426.  
  427. const partContent = currentText.substring(0, endPos);
  428. totalProcessed += partContent.length;
  429. currentText = currentText.substring(endPos);
  430.  
  431. let chapterTitle = title;
  432. if (title.includes(':')) {
  433. chapterTitle = title.substring(title.indexOf(':') + 1).trim();
  434. }
  435.  
  436. let newTitle = `${title} (Phn ${part+1}/${parts})`;
  437. processedChapters.push(newTitle + '\n' + partContent);
  438. console.log(`Part ${part+1}: ${partContent.length} chars`);
  439. }
  440.  
  441. console.log(`Total processed: ${totalProcessed}/${charCount} chars`);
  442. } else {
  443. processedChapters.push(chapters[i]);
  444. }
  445. }
  446.  
  447. console.log(`After processing: ${processedChapters.length} chapters`);
  448. return processedChapters;
  449. },
  450.  
  451. handleRemainingChapters: function(remainingChapters, originalChapters) {
  452. try {
  453. const clipboardContent = remainingChapters.map(chap => {
  454. const lines = chap.trim().split('\n');
  455. if (lines.length > 0) {
  456. if (!lines[0].startsWith('\t')) {
  457. lines[0] = '\t' + lines[0];
  458. }
  459. }
  460. return lines.join('\n');
  461. }).join('\n\n---CHAPTER_SEPARATOR---\n\n');
  462.  
  463. if (navigator.clipboard && navigator.clipboard.writeText) {
  464. navigator.clipboard.writeText(clipboardContent)
  465. .then(() => {
  466. console.log(`Đã lưu li ${remainingChapters.length} Chương vào clipboard`);
  467. this.showChaptersReport(originalChapters, remainingChapters, true);
  468. })
  469. .catch(err => {
  470. throw err;
  471. });
  472. } else {
  473. this.fallbackClipboardCopy(clipboardContent);
  474. this.showChaptersReport(originalChapters, remainingChapters, true);
  475. }
  476. } catch (err) {
  477. console.error('Lỗi khi sao chép vào clipboard:', err);
  478. this.showManualCopyOption(remainingChapters);
  479. this.showChaptersReport(originalChapters, remainingChapters, true);
  480. }
  481. },
  482.  
  483. fallbackClipboardCopy: function(content) {
  484. const tempTextarea = document.createElement('textarea');
  485. tempTextarea.style.position = 'fixed';
  486. tempTextarea.style.top = '0';
  487. tempTextarea.style.left = '0';
  488. tempTextarea.style.width = '2em';
  489. tempTextarea.style.height = '2em';
  490. tempTextarea.style.opacity = '0';
  491. tempTextarea.style.pointerEvents = 'none';
  492. tempTextarea.value = content;
  493. document.body.appendChild(tempTextarea);
  494. tempTextarea.focus();
  495. tempTextarea.select();
  496. const successful = document.execCommand('copy');
  497. document.body.removeChild(tempTextarea);
  498.  
  499. if (!successful) {
  500. throw new Error('Không thể sao chép vào clipboard');
  501. }
  502.  
  503. console.log(`Đã sao chép ni dung vào clipboard (execCommand)`);
  504. },
  505.  
  506. showManualCopyOption: function(remainingChapters) {
  507. const clipboardContent = remainingChapters.map(chap => {
  508. const lines = chap.trim().split('\n');
  509. if (lines.length > 0) {
  510. if (!lines[0].startsWith('\t')) {
  511. lines[0] = '\t' + lines[0];
  512. }
  513. }
  514. return lines.join('\n');
  515. }).join('\n\n---CHAPTER_SEPARATOR---\n\n');
  516.  
  517. const manualCopyArea = document.createElement('div');
  518. manualCopyArea.style.position = 'fixed';
  519. manualCopyArea.style.top = '50%';
  520. manualCopyArea.style.left = '50%';
  521. manualCopyArea.style.transform = 'translate(-50%, -50%)';
  522. manualCopyArea.style.backgroundColor = 'white';
  523. manualCopyArea.style.padding = '20px';
  524. manualCopyArea.style.borderRadius = '8px';
  525. manualCopyArea.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
  526. manualCopyArea.style.zIndex = '10000';
  527. manualCopyArea.style.maxWidth = '80%';
  528. manualCopyArea.style.maxHeight = '80%';
  529. manualCopyArea.style.overflow = 'auto';
  530. manualCopyArea.innerHTML = `
  531. <h3 style="margin-top: 0;">Sao chép th công</h3>
  532. <p>Không th sao chép t động. Vui lòng chn toàn b ni dung bên dưới và sao chép (Ctrl+C):</p>
  533. <textarea style="width: 100%; height: 300px; padding: 10px;">${clipboardContent}</textarea>
  534. <div style="text-align: right; margin-top: 10px;">
  535. <button id="closeManualCopy" style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">Đóng</button>
  536. </div>
  537. `;
  538.  
  539. document.body.appendChild(manualCopyArea);
  540.  
  541. document.getElementById('closeManualCopy').addEventListener('click', () => {
  542. document.body.removeChild(manualCopyArea);
  543. });
  544. },
  545.  
  546. showChaptersReport: function(originalChapters, processedChapters, hasRemainingChapters = false) {
  547. let splitChapters = 0;
  548. let longChapterDetails = [];
  549.  
  550. for (let i = 0; i < originalChapters.length; i++) {
  551. const chapterLines = originalChapters[i].split('\n');
  552. const title = chapterLines.shift().trim();
  553. const chapterText = chapterLines.join('\n');
  554.  
  555. if (chapterText.length > 40000) {
  556. splitChapters++;
  557. const partsCount = Math.ceil(chapterText.length / 40000);
  558. longChapterDetails.push({
  559. title: title,
  560. parts: partsCount
  561. });
  562. }
  563. }
  564.  
  565. const splittedChaptersCount = processedChapters.length - (originalChapters.length - splitChapters);
  566. let message = '';
  567.  
  568. message = message.concat(`📝Đã x lý ${originalChapters.length} Chương gc\n`);
  569. message = message.concat(`📝Đã nhp ${Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`);
  570.  
  571. if (hasRemainingChapters) {
  572. message = message.concat(`📋Đã lưu li ${processedChapters.length - Math.min(processedChapters.length, MAX_CHAPTER_POST)} Chương\n`);
  573. }
  574.  
  575. if (splitChapters > 0) {
  576. message = message.concat(`📑Có ${splitChapters} Chương đã chia thành ${splittedChaptersCount} Chương\n`);
  577. longChapterDetails.forEach(chapter => {
  578. let chapterName = chapter.title;
  579. if (chapterName.includes(':')) {
  580. chapterName = chapterName.trim();
  581. }
  582. message = message.concat(` - ${chapterName}: ${chapter.parts} Chương\n`);
  583. });
  584. }
  585.  
  586. showNotification(message, 'success');
  587. },
  588.  
  589. removeEmptyChapters: function() {
  590. const forms = document.querySelectorAll('[data-gen="MK_GEN"]');
  591. let removed = 0;
  592.  
  593. forms.forEach(form => {
  594. const content = form.querySelector('textarea[name^="introduce"]').value.trim();
  595. if (!content) {
  596. form.remove();
  597. removed++;
  598. this.updateChapNumber(false);
  599. }
  600. });
  601.  
  602. showNotification(`Đã xóa ${removed} chương trng t ${forms.length} chương`, 'info');
  603. },
  604.  
  605. submitChapters: function() {
  606. this.showLoading();
  607. document.querySelector('form[name="postChapForm"] button[type="submit"]').click();
  608. setTimeout(() => this.hideLoading(), 2000);
  609. },
  610.  
  611. addNewChapter: function() {
  612. if ((this.STATE.CHAP_NUMBER + 1) <= MAX_CHAPTER_POST) {
  613. this.updateChapNumber(true);
  614. const html = createChapterHTML(this.STATE.CHAP_NUMBER);
  615. jQuery('#add-chap').before(html);
  616. } else {
  617. showNotification(`Ch có th đăng ti đa ${MAX_CHAPTER_POST} chương mt ln`, 'warning');
  618. }
  619. },
  620.  
  621. updateChapNumber: function(isAdd) {
  622. try {
  623. if (isAdd) {
  624. let maxStt = 0;
  625. let maxSerial = 0;
  626. jQuery('input[name^="chap_stt"]').each(function() {
  627. const val = parseInt(jQuery(this).val()) || 0;
  628. maxStt = Math.max(maxStt, val);
  629. });
  630. jQuery('input[name^="chap_number"]').each(function() {
  631. const val = parseInt(jQuery(this).val()) || 0;
  632. maxSerial = Math.max(maxSerial, val);
  633. });
  634. const chapStt = parseInt(jQuery('.chap_stt1').val()) || 0;
  635. const chapSerial = parseInt(jQuery('.chap_serial').val()) || 0;
  636. maxStt = Math.max(maxStt, chapStt);
  637. maxSerial = Math.max(maxSerial, chapSerial);
  638. this.STATE.CHAP_STT = maxStt + 1;
  639. this.STATE.CHAP_SERIAL = maxSerial + 1;
  640. this.STATE.CHAP_NUMBER++;
  641. } else {
  642. if (this.STATE.CHAP_NUMBER > this.STATE.CHAP_NUMBER_ORIGINAL) {
  643. this.STATE.CHAP_NUMBER--;
  644. }
  645. if (this.STATE.CHAP_STT > this.STATE.CHAP_STT_ORIGINAL) {
  646. this.STATE.CHAP_STT--;
  647. }
  648. if (this.STATE.CHAP_SERIAL > this.STATE.CHAP_SERIAL_ORIGINAL) {
  649. this.STATE.CHAP_SERIAL--;
  650. }
  651. }
  652. jQuery('#chap_number').val(this.STATE.CHAP_NUMBER);
  653. jQuery('#chap_stt').val(this.STATE.CHAP_STT);
  654. jQuery('#chap_serial').val(this.STATE.CHAP_SERIAL);
  655. jQuery('#countNumberPost').text(this.STATE.CHAP_NUMBER);
  656. } catch (e) {
  657. console.log("Lỗi: " + e);
  658. }
  659. },
  660.  
  661. showLoading: function() {
  662. const loading = jQuery("<div>", {
  663. class: "loading-overlay",
  664. html: "<div class='loading-spinner'></div>"
  665. });
  666. jQuery("body").append(loading);
  667. },
  668.  
  669. hideLoading: function() {
  670. jQuery(".loading-overlay").remove();
  671. }
  672. };
  673.  
  674. // Tạo HTML cho form chương
  675. function createChapterHTML(chapNum) {
  676. const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
  677. const chap_vol_name = jQuery('.chap_vol_name').val() || '';
  678. return `
  679. <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK">
  680. <div class="col-xs-12 form-group"></div>
  681. <div class="form-group">
  682. <label class="col-sm-2" for="chap_stt">STT</label>
  683. <div class="col-sm-8">
  684. <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"/>
  685. </div>
  686. </div>
  687. <div class="form-group">
  688. <label class="col-sm-2" for="chap_number">Chương thứ..</label>
  689. <div class="col-sm-8">
  690. <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/>
  691. </div>
  692. </div>
  693. <div class="form-group">
  694. <label class="col-sm-2" for="chap_name">Quyn số</label>
  695. <div class="col-sm-8">
  696. <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/>
  697. </div>
  698. </div>
  699. <div class="form-group">
  700. <label class="col-sm-2" for="chap_name">Tên quyn</label>
  701. <div class="col-sm-8">
  702. <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" />
  703. </div>
  704. </div>
  705. <div class="form-group">
  706. <label class="col-sm-2" for="chap_name">Tên chương</label>
  707. <div class="col-sm-8">
  708. <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/>
  709. </div>
  710. </div>
  711. <div class="form-group">
  712. <label class="col-sm-2" for="introduce">Ni dung</label>
  713. <div class="col-sm-8">
  714. <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>
  715. </div>
  716. </div>
  717. <div class="form-group">
  718. <label class="col-sm-2" for="adv">Qung cáo</label>
  719. <div class="col-sm-8">
  720. <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea>
  721. </div>
  722. </div>
  723. </div>`;
  724. }
  725.  
  726. // Khởi động script
  727. jQuery(document).ready(function() {
  728. dăngnhanhTTV.init();
  729. });
  730. })();