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 4.9
  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. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // CSS tối giản
  15. const style = document.createElement('style');
  16. style.textContent = `
  17. .ttv-notification {position:fixed;top:20px;right:20px;padding:10px 20px;background:#4CAF50;color:white;border-radius:4px;z-index:9999;display:none}
  18. .ttv-error {background:#f44336}
  19. .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}
  20. .ttv-control-panel.minimized {width:auto;height:auto;padding:10px;opacity:0.8;transform:translateX(calc(100% - 40px))}
  21. .ttv-control-panel.minimized:hover {opacity:1;transform:translateX(0)}
  22. .ttv-control-panel.minimized .ttv-button-group,.ttv-control-panel.minimized .ttv-header {display:none}
  23. .ttv-button-group {display:flex;flex-direction:column;gap:15px}
  24. .ttv-content-editor {width:100%;height:100px;margin:6px 0;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:13px;resize:vertical}
  25. .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}
  26. .ttv-heading {font-size:15px;color:#555;margin:12px 0;display:flex;justify-content:space-between;align-items:center}
  27. .ttv-word-count {font-size:12px;color:#888;padding:3px 8px;background:#f5f5f5;border-radius:4px}
  28. .ttv-chapter-list {width:100%;margin:10px 0;max-height:200px;overflow-y:auto;border:1px solid #eee;border-radius:6px;padding:8px}
  29. .ttv-chapter-item {padding:8px;border-bottom:1px solid #eee;cursor:pointer;font-size:12px;color:#666}
  30. .ttv-chapter-item .chapter-title {font-weight:bold;margin-bottom:4px}
  31. .ttv-chapter-item .chapter-name {color:#888;padding-left:10px;border-left:2px solid #ddd;margin:4px 0}
  32. .ttv-chapter-item .chapter-stats {font-size:11px;color:#999}
  33. .ttv-chapter-item:last-child {border-bottom:none}
  34. .ttv-chapter-item.selected {background:#f0f8ff;border-left:2px solid #5bc0de}
  35. button.btn-warning {background:#f0ad4e;color:white;border:none;padding:12px;border-radius:6px;width:100%;font-size:14px}
  36. button.btn-warning:hover {background:#ec971f}
  37. button.ttv-minimize {padding:2px 8px;background:none;border:none;cursor:pointer;font-size:16px;color:#666}
  38. `;
  39. document.head.appendChild(style);
  40.  
  41. // State management
  42. const dăngnhanhTTV = {
  43. STATE: {
  44. CHAP_NUMBER: 1,
  45. CHAP_STT: 1,
  46. CHAP_SERIAL: 1,
  47. CHAP_NUMBER_ORIGINAL: 1,
  48. CHAP_STT_ORIGINAL: 1,
  49. CHAP_SERIAL_ORIGINAL: 1
  50. },
  51. initializeChapterValues: function() {
  52. try {
  53. const chap_number = parseInt(jQuery('#chap_number').val());
  54. let chap_stt = parseInt(jQuery('.chap_stt1').val());
  55. let chap_serial = parseInt(jQuery('.chap_serial').val());
  56.  
  57. if (parseInt(jQuery('#chap_stt').val()) > chap_stt) {
  58. chap_stt = parseInt(jQuery('#chap_stt').val());
  59. }
  60. if (parseInt(jQuery('#chap_serial').val()) > chap_serial) {
  61. chap_serial = parseInt(jQuery('#chap_serial').val());
  62. }
  63.  
  64. this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1;
  65. this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1;
  66. this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1;
  67. } catch (e) {
  68. console.error("Error initializing chapter values:", e);
  69. }
  70. }
  71. };
  72.  
  73. // UI Elements
  74. const notification = document.createElement('div');
  75. notification.className = 'ttv-notification';
  76. document.body.appendChild(notification);
  77.  
  78. // Hiển thị thông báo
  79. function showNotification(message, isError = false) {
  80. notification.textContent = message;
  81. notification.className = 'ttv-notification' + (isError ? ' ttv-error' : '');
  82. notification.style.display = 'block';
  83. setTimeout(() => notification.style.display = 'none', 3000);
  84. }
  85.  
  86. // Phân tích chương
  87. function parseChapters(content) {
  88. // 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
  89. const lines = content.split('\n');
  90. const chapters = [];
  91. let currentChapter = {title: '', name: '', content: []};
  92. const chapterPattern = /^\s*Chương\s+\d+:/;
  93.  
  94. // Biến để kiểm soát tiêu đề trùng lặp
  95. let chapterTitles = new Set();
  96. let chapterNumbers = new Map(); // Lưu số chương đã gặp
  97. let duplicateCount = 0;
  98.  
  99. // Mảng lưu nội dung của các dòng trống gần nhất
  100. let emptyLineBuffer = [];
  101. // Biến để theo dõi xem ta có đang ở trong một khối tiêu đề lặp không
  102. let inDuplicatedTitleBlock = false;
  103. // Theo dõi tiêu đề chương trước đó
  104. let previousChapterLine = '';
  105. // Khoảng cách dòng tối thiểu giữa các tiêu đề hợp lệ
  106. const MIN_LINES_BETWEEN_CHAPTERS = 5;
  107. // Số dòng đã qua kể từ tiêu đề cuối
  108. let linesSinceLastTitle = 0;
  109.  
  110. for (let i = 0; i < lines.length; i++) {
  111. const line = lines[i];
  112. // Lấy dòng chính xác, không thay đổi định dạng
  113. const exactLine = lines[i];
  114. // Chỉ trim để kiểm tra điều kiện
  115. const trimmedLine = line.trim();
  116. linesSinceLastTitle++;
  117.  
  118. // Xử lý dòng trống - giữ đúng định dạng gốc
  119. if (trimmedLine === '') {
  120. emptyLineBuffer.push(exactLine); // Lưu dòng trống với định dạng gốc
  121. continue;
  122. }
  123.  
  124. if (chapterPattern.test(trimmedLine)) {
  125. // Trích xuất số chương từ tiêu đề
  126. const chapterMatch = trimmedLine.match(/Chương\s+(\d+):/);
  127. const chapterNum = chapterMatch ? parseInt(chapterMatch[1]) : 0;
  128. const chapterName = trimmedLine.split(':')[1]?.trim() || '';
  129.  
  130. // Kiểm tra các điều kiện để xác định tiêu đề chương hợp lệ
  131. const isPotentialChapter = linesSinceLastTitle > MIN_LINES_BETWEEN_CHAPTERS || chapters.length === 0;
  132. const isFullTitle = chapterName.length > 2; // Tiêu đề đầy đủ phải có ít nhất 3 ký tự sau dấu :
  133. const isDuplicateOfPrevious = trimmedLine === previousChapterLine;
  134.  
  135. if (isPotentialChapter && isFullTitle && !isDuplicateOfPrevious) {
  136. // Lưu chương hiện tại nếu có
  137. if (currentChapter.title && currentChapter.content.length > 0) {
  138. chapters.push({...currentChapter});
  139. currentChapter = {title: '', name: '', content: []};
  140. }
  141.  
  142. // Khởi tạo chương mới - giữ nguyên định dạng tiêu đề
  143. currentChapter.title = exactLine;
  144. let name = chapterName;
  145. name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim();
  146. currentChapter.name = name;
  147.  
  148. // Đánh dấu đã gặp tiêu đề và số chương này
  149. chapterTitles.add(exactLine);
  150. chapterNumbers.set(chapterNum, true);
  151.  
  152. // Đặt lại các biến theo dõi
  153. inDuplicatedTitleBlock = false;
  154. linesSinceLastTitle = 0;
  155. previousChapterLine = trimmedLine;
  156. emptyLineBuffer = [];
  157. } else {
  158. // Đây là một tiêu đề bị lặp, xử lý như nội dung chương thông thường
  159. if (currentChapter.title) {
  160. // Thêm các dòng trống đã lưu trữ với định dạng gốc
  161. currentChapter.content.push(...emptyLineBuffer);
  162. emptyLineBuffer = [];
  163. // Thêm dòng hiện tại với định dạng gốc
  164. currentChapter.content.push(exactLine);
  165. }
  166. inDuplicatedTitleBlock = true;
  167. duplicateCount++;
  168. }
  169. } else if (currentChapter.title) {
  170. // Giữ nguyên định dạng gốc của dòng (không trim),
  171. // bao gồm cả khoảng trắng, xuống dòng và định dạng đặc biệt
  172. currentChapter.content.push(exactLine);
  173. }
  174. }
  175.  
  176. if (currentChapter.title && currentChapter.content.length > 0) {
  177. chapters.push({...currentChapter});
  178. }
  179.  
  180. if (chapters.length > 0) {
  181. displayChapters(chapters);
  182. if (duplicateCount > 0) {
  183. showNotification(`Đã tìm thy ${chapters.length} chương và b qua ${duplicateCount} tiêu đề lp li.`);
  184. } else {
  185. showNotification(`Đã tìm thy ${chapters.length} chương và t động đin vào form.`);
  186. }
  187.  
  188. setTimeout(() => {
  189. try {
  190. transferContent();
  191. } catch (error) {
  192. console.error('Lỗi khi tự động điền form:', error);
  193. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  194. }
  195. }, 1000);
  196. }
  197.  
  198. return chapters;
  199. }
  200.  
  201. // Tạo HTML cho form chương
  202. function createChapterHTML(chapNum) {
  203. const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
  204. const chap_vol_name = jQuery('.chap_vol_name').val() || '';
  205. return `
  206. <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK">
  207. <div class="col-xs-12 form-group"></div>
  208. <div class="form-group">
  209. <label class="col-sm-2" for="chap_stt">STT</label>
  210. <div class="col-sm-8">
  211. <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"/>
  212. </div>
  213. </div>
  214. <div class="form-group">
  215. <label class="col-sm-2" for="chap_number">Chương thứ..</label>
  216. <div class="col-sm-8">
  217. <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/>
  218. </div>
  219. </div>
  220. <div class="form-group">
  221. <label class="col-sm-2" for="chap_name">Quyn số</label>
  222. <div class="col-sm-8">
  223. <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/>
  224. </div>
  225. </div>
  226. <div class="form-group">
  227. <label class="col-sm-2" for="chap_name">Tên quyn</label>
  228. <div class="col-sm-8">
  229. <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" />
  230. </div>
  231. </div>
  232. <div class="form-group">
  233. <label class="col-sm-2" for="chap_name">Tên chương</label>
  234. <div class="col-sm-8">
  235. <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/>
  236. </div>
  237. </div>
  238. <div class="form-group">
  239. <label class="col-sm-2" for="introduce">Ni dung</label>
  240. <div class="col-sm-8">
  241. <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>
  242. <div class="chapter-character-count"></div>
  243. </div>
  244. </div>
  245. <div class="form-group">
  246. <label class="col-sm-2" for="adv">Qung cáo</label>
  247. <div class="col-sm-8">
  248. <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea>
  249. </div>
  250. </div>
  251. </div>`;
  252. }
  253.  
  254. // Chuyển nội dung vào form
  255. async function transferContent() {
  256. try {
  257. const chapterItems = document.querySelectorAll('.ttv-chapter-item');
  258. if (!chapterItems.length) {
  259. throw new Error('Không tìm thấy chương nào để điền vào form');
  260. }
  261.  
  262. const form = document.querySelector('form[name="postChapForm"]');
  263. if (!form) {
  264. throw new Error('Không tìm thấy form đăng chương');
  265. }
  266.  
  267. let chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
  268. let chap_vol_name = jQuery('.chap_vol_name').val() || '';
  269.  
  270. let maxChapStt = 0;
  271. let maxChapSerial = 0;
  272.  
  273. const existingForms = form.querySelectorAll('[id^="COUNT_CHAP_"]');
  274.  
  275. existingForms.forEach(formElem => {
  276. const formIdMatch = formElem.id.match(/COUNT_CHAP_(\d+)_MK/);
  277. if (formIdMatch && formIdMatch[1]) {
  278. const formIndex = parseInt(formIdMatch[1]);
  279.  
  280. const sttInput = formElem.querySelector(`input[name="chap_stt[${formIndex}]"]`);
  281. if (sttInput && sttInput.value && !isNaN(parseInt(sttInput.value))) {
  282. const sttVal = parseInt(sttInput.value);
  283. if (sttVal > maxChapStt) {
  284. maxChapStt = sttVal;
  285. }
  286. }
  287.  
  288. const serialInput = formElem.querySelector(`input[name="chap_number[${formIndex}]"]`);
  289. if (serialInput && serialInput.value && !isNaN(parseInt(serialInput.value))) {
  290. const serialVal = parseInt(serialInput.value);
  291. if (serialVal > maxChapSerial) {
  292. maxChapSerial = serialVal;
  293. }
  294. }
  295. }
  296. });
  297.  
  298. // Đảm bảo STT và số chương tiếp theo form gốc
  299. if (maxChapStt > 0) {
  300. dăngnhanhTTV.STATE.CHAP_STT = maxChapStt;
  301. }
  302.  
  303. if (maxChapSerial > 0) {
  304. dăngnhanhTTV.STATE.CHAP_SERIAL = maxChapSerial;
  305. }
  306.  
  307. const existingFormCount = existingForms.length;
  308.  
  309. for (let i = 0; i < chapterItems.length; i++) {
  310. const formIndex = existingFormCount + i + 1;
  311. // Tăng STT và Serial cho mỗi chương mới
  312. if (i > 0) {
  313. dăngnhanhTTV.STATE.CHAP_STT++;
  314. dăngnhanhTTV.STATE.CHAP_SERIAL++;
  315. }
  316.  
  317. const chapterHTML = createChapterHTML(formIndex);
  318. const tempDiv = document.createElement('div');
  319. tempDiv.innerHTML = chapterHTML;
  320. const newFormElement = tempDiv.firstElementChild;
  321.  
  322. if (!newFormElement) {
  323. throw new Error(`Không th to element form cho chương ${formIndex}`);
  324. }
  325.  
  326. form.appendChild(newFormElement);
  327.  
  328. const chapterItem = chapterItems[i];
  329. const titleElement = chapterItem.querySelector('.chapter-title');
  330. const nameElement = chapterItem.querySelector('.chapter-name');
  331.  
  332. if (!titleElement || !nameElement) {
  333. throw new Error(`Thiếu thông tin tiêu đề hoc tên cho chương ${formIndex}`);
  334. }
  335.  
  336. const chapterTitle = titleElement.textContent;
  337. const chapterName = nameElement.textContent.replace('Tên chương: ', '');
  338.  
  339. const formFields = {
  340. chapterName: form.querySelector(`input[name="chap_name[${formIndex}]"]`),
  341. content: form.querySelector(`textarea[name="introduce[${formIndex}]"]`),
  342. chapterNumber: form.querySelector(`input[name="chap_number[${formIndex}]"]`),
  343. chapterOrder: form.querySelector(`input[name="chap_stt[${formIndex}]"]`),
  344. volume: form.querySelector(`input[name="vol[${formIndex}]"]`),
  345. volumeName: form.querySelector(`input[name="vol_name[${formIndex}]"]`),
  346. advertisement: form.querySelector(`textarea[name="adv[${formIndex}]"]`)
  347. };
  348.  
  349. formFields.chapterName.value = chapterName;
  350. formFields.chapterNumber.value = dăngnhanhTTV.STATE.CHAP_SERIAL.toString();
  351. formFields.chapterOrder.value = dăngnhanhTTV.STATE.CHAP_STT.toString();
  352. formFields.volume.value = chap_vol;
  353. formFields.volumeName.value = chap_vol_name;
  354.  
  355. if (chapterItem._content) {
  356. // Chỉ lấy phần nội dung, không lấy phần tiêu đề
  357. // Đảm bảo giữ nguyên định dạng gốc của nội dung, không trim
  358. formFields.content.value = chapterItem._content;
  359. } else {
  360. formFields.content.value = '';
  361. }
  362.  
  363. formFields.advertisement.value = '';
  364.  
  365. const inputEvent = new Event('input', { bubbles: true });
  366. formFields.content.dispatchEvent(inputEvent);
  367. }
  368.  
  369. showNotification(`Đã t động đin ${chapterItems.length} chương vào form!`);
  370.  
  371. } catch (error) {
  372. console.error('Lỗi khi điền form:', error);
  373. showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true);
  374. }
  375. }
  376.  
  377. // Hiển thị danh sách chương
  378. function displayChapters(chapters) {
  379. const chapterList = document.createElement('div');
  380. chapterList.className = 'ttv-chapter-list';
  381.  
  382. chapters.forEach((chapter, index) => {
  383. const chapterItem = document.createElement('div');
  384. chapterItem.className = 'ttv-chapter-item';
  385. // Chỉ lấy phần nội dung dưới tiêu đề, không bao gồm tiêu đề
  386. chapterItem._content = chapter.content.join('\n');
  387.  
  388. // Trích xuất số chương để hiển thị
  389. const chapterMatch = chapter.title.match(/Chương\s+(\d+):/);
  390. const chapterNum = chapterMatch ? chapterMatch[1] : '?';
  391.  
  392. const titleDiv = document.createElement('div');
  393. titleDiv.className = 'chapter-title';
  394. titleDiv.textContent = chapter.title;
  395.  
  396. const nameDiv = document.createElement('div');
  397. nameDiv.className = 'chapter-name';
  398. nameDiv.textContent = `Tên chương: ${chapter.name}`;
  399.  
  400. const statsDiv = document.createElement('div');
  401. statsDiv.className = 'chapter-stats';
  402. statsDiv.textContent = `Chương ${chapterNum} | ${chapter.content.length} dòng`;
  403.  
  404. chapterItem.appendChild(titleDiv);
  405. chapterItem.appendChild(nameDiv);
  406. chapterItem.appendChild(statsDiv);
  407. chapterItem.onclick = () => selectChapter(chapter, index);
  408. chapterList.appendChild(chapterItem);
  409.  
  410. if (index === 0) {
  411. chapterItem.classList.add('selected');
  412. }
  413. });
  414.  
  415. const existingList = document.querySelector('.ttv-chapter-list');
  416. if (existingList) {
  417. existingList.remove();
  418. }
  419.  
  420. const contentEditor = document.querySelector('.ttv-content-editor');
  421. contentEditor.parentNode.insertBefore(chapterList, contentEditor);
  422.  
  423. if (chapters.length > 0) {
  424. selectChapter(chapters[0], 0);
  425. }
  426. }
  427.  
  428. // Chọn chương
  429. function selectChapter(chapter, index) {
  430. const contentEditor = document.querySelector('.ttv-content-editor');
  431. // Chỉ lấy nội dung, không lấy tiêu đề để tránh lặp lại
  432. contentEditor.value = chapter.content.join('\n');
  433.  
  434. const wordCountSpan = document.querySelector('.ttv-word-count');
  435. if (wordCountSpan) {
  436. wordCountSpan.textContent = updateWordCount(contentEditor.value);
  437. }
  438.  
  439. const chapterItems = document.querySelectorAll('.ttv-chapter-item');
  440. chapterItems.forEach(item => item.classList.remove('selected'));
  441. chapterItems[index]?.classList.add('selected');
  442.  
  443. try {
  444. transferContent();
  445. } catch (error) {
  446. console.error('Lỗi khi tự động điền form:', error);
  447. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  448. }
  449. }
  450.  
  451. // Tính số từ
  452. function updateWordCount(content) {
  453. const wordCount = content.trim().split(/\s+/).length;
  454. const charCount = content.length;
  455. return `${wordCount} t | ${charCount} ký tự`;
  456. }
  457.  
  458. // Xem trước
  459. function togglePreview() {
  460. const contentEditor = document.querySelector('.ttv-content-editor');
  461. const preview = document.querySelector('.ttv-preview');
  462. const previewBtn = document.querySelector('.ttv-preview-btn');
  463.  
  464. if (contentEditor.style.display !== 'none') {
  465. contentEditor.style.display = 'none';
  466. preview.style.display = 'block';
  467. preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>');
  468. previewBtn.textContent = 'Soạn thảo';
  469. } else {
  470. contentEditor.style.display = 'block';
  471. preview.style.display = 'none';
  472. previewBtn.textContent = 'Xem trước';
  473. }
  474. }
  475.  
  476. // Toàn màn hình
  477. function toggleFullscreen() {
  478. const panel = document.querySelector('.ttv-control-panel');
  479. const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn');
  480. panel.classList.toggle('fullscreen');
  481. fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình';
  482. }
  483.  
  484. // Tạo panel điều khiển
  485. function addControlPanel() {
  486. const panel = document.createElement('div');
  487. panel.className = 'ttv-control-panel';
  488.  
  489. const header = document.createElement('div');
  490. header.className = 'ttv-header';
  491. header.innerHTML = `
  492. <div>Son Tho Ni Dung</div>
  493. <div class="ttv-toolbar">
  494. <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button>
  495. <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button>
  496. <button class="ttv-minimize">−</button>
  497. </div>
  498. `;
  499.  
  500. const buttonGroup = document.createElement('div');
  501. buttonGroup.className = 'ttv-button-group';
  502.  
  503. const contentEditorLabel = document.createElement('div');
  504. contentEditorLabel.className = 'ttv-heading';
  505. contentEditorLabel.innerHTML = `
  506. Ni dung chương:
  507. <span class="ttv-word-count">0 t | 0 ký tự</span>
  508. `;
  509.  
  510. const contentEditor = document.createElement('textarea');
  511. contentEditor.className = 'ttv-content-editor';
  512. contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...';
  513.  
  514. const preview = document.createElement('div');
  515. preview.className = 'ttv-preview';
  516.  
  517. contentEditor.oninput = () => {
  518. const wordCountSpan = document.querySelector('.ttv-word-count');
  519. if (wordCountSpan) {
  520. wordCountSpan.textContent = updateWordCount(contentEditor.value);
  521. }
  522. };
  523.  
  524. contentEditor.onpaste = (e) => {
  525. setTimeout(() => {
  526. const content = contentEditor.value;
  527. const chapters = parseChapters(content);
  528. if (chapters.length > 0) {
  529. displayChapters(chapters);
  530. setTimeout(() => {
  531. try {
  532. transferContent();
  533. } catch (error) {
  534. console.error('Lỗi khi tự động điền form:', error);
  535. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  536. }
  537. }, 1000);
  538. }
  539. }, 0);
  540. };
  541.  
  542. const transferBtn = document.createElement('button');
  543. transferBtn.type = 'button';
  544. transferBtn.className = 'btn btn-warning';
  545. transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>';
  546. transferBtn.onclick = transferContent;
  547.  
  548. panel.appendChild(header);
  549. buttonGroup.appendChild(contentEditorLabel);
  550. buttonGroup.appendChild(contentEditor);
  551. buttonGroup.appendChild(preview);
  552. buttonGroup.appendChild(transferBtn);
  553. panel.appendChild(buttonGroup);
  554.  
  555. document.body.appendChild(panel);
  556.  
  557. const minimizeBtn = panel.querySelector('.ttv-minimize');
  558. minimizeBtn.onclick = () => {
  559. panel.classList.toggle('minimized');
  560. minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−';
  561. };
  562.  
  563. window.togglePreview = togglePreview;
  564. window.toggleFullscreen = toggleFullscreen;
  565. }
  566.  
  567. window.addEventListener('load', function() {
  568. dăngnhanhTTV.initializeChapterValues();
  569. addControlPanel();
  570. });
  571. })();