TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao

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

  1. // ==UserScript==
  2. // @name TTV Auto Upload
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.7
  5. // @description Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao
  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 cho thông báo và control panel
  15. const style = document.createElement('style');
  16. style.textContent = `
  17. .ttv-notification {
  18. position: fixed;
  19. top: 20px;
  20. right: 20px;
  21. padding: 10px 20px;
  22. background: #4CAF50;
  23. color: white;
  24. border-radius: 4px;
  25. z-index: 9999;
  26. display: none;
  27. }
  28. .ttv-error {
  29. background: #f44336;
  30. }
  31. .ttv-control-panel {
  32. position: fixed;
  33. top: 50px;
  34. right: 20px;
  35. background: white;
  36. padding: 25px;
  37. border-radius: 12px;
  38. box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  39. z-index: 9998;
  40. width: 500px;
  41. margin-bottom: 20px;
  42. transition: all 0.3s ease;
  43. }
  44. .ttv-control-panel.minimized {
  45. width: auto;
  46. height: auto;
  47. padding: 10px;
  48. opacity: 0.8;
  49. transform: translateX(calc(100% - 40px));
  50. transition: all 0.3s ease;
  51. }
  52. .ttv-control-panel.minimized:hover {
  53. opacity: 1;
  54. transform: translateX(0);
  55. }
  56. .ttv-control-panel.minimized .ttv-button-group,
  57. .ttv-control-panel.minimized .ttv-header {
  58. display: none;
  59. }
  60. .ttv-button-group {
  61. display: flex;
  62. flex-direction: column;
  63. gap: 15px;
  64. }
  65. .ttv-content-editor {
  66. width: 100%;
  67. height: 100px;
  68. margin: 6px 0;
  69. padding: 8px;
  70. border: 1px solid #ddd;
  71. border-radius: 6px;
  72. font-size: 13px;
  73. line-height: 1.4;
  74. resize: vertical;
  75. transition: all 0.2s ease;
  76. }
  77. .ttv-content-editor:focus {
  78. border-color: #5bc0de;
  79. outline: none;
  80. box-shadow: 0 0 8px rgba(91,192,222,0.2);
  81. }
  82. .ttv-preview {
  83. display: none;
  84. width: 100%;
  85. height: 100px;
  86. margin: 6px 0;
  87. padding: 8px;
  88. border: 1px solid #ddd;
  89. border-radius: 6px;
  90. font-size: 13px;
  91. line-height: 1.4;
  92. overflow-y: auto;
  93. background: #f9f9f9;
  94. transition: all 0.2s ease;
  95. }
  96. .ttv-heading {
  97. font-size: 15px;
  98. color: #555;
  99. margin: 12px 0;
  100. display: flex;
  101. justify-content: space-between;
  102. align-items: center;
  103. }
  104. .ttv-word-count {
  105. font-size: 12px;
  106. color: #888;
  107. padding: 3px 8px;
  108. background: #f5f5f5;
  109. border-radius: 4px;
  110. transition: all 0.2s ease;
  111. }
  112. .ttv-chapter-list {
  113. width: 100%;
  114. margin: 10px 0;
  115. max-height: 200px;
  116. overflow-y: auto;
  117. border: 1px solid #eee;
  118. border-radius: 6px;
  119. padding: 8px;
  120. }
  121. .ttv-chapter-item {
  122. padding: 8px;
  123. border-bottom: 1px solid #eee;
  124. cursor: pointer;
  125. transition: all 0.2s ease;
  126. line-height: 1.4;
  127. font-size: 12px;
  128. color: #666;
  129. }
  130. .ttv-chapter-item .chapter-title {
  131. font-weight: bold;
  132. margin-bottom: 4px;
  133. }
  134. .ttv-chapter-item .chapter-name {
  135. color: #888;
  136. padding-left: 10px;
  137. border-left: 2px solid #ddd;
  138. margin: 4px 0;
  139. }
  140. .ttv-chapter-item .chapter-stats {
  141. font-size: 11px;
  142. color: #999;
  143. }
  144. .ttv-chapter-item:last-child {
  145. border-bottom: none;
  146. }
  147. .ttv-chapter-item.selected {
  148. background: #f0f8ff;
  149. border-left: 2px solid #5bc0de;
  150. }
  151. .ttv-control-panel.fullscreen {
  152. position: fixed;
  153. top: 0;
  154. right: 0;
  155. bottom: 0;
  156. left: 0;
  157. width: 100%;
  158. height: 100%;
  159. border-radius: 0;
  160. z-index: 9999;
  161. padding: 15px;
  162. display: flex;
  163. flex-direction: column;
  164. }
  165. .ttv-control-panel.fullscreen .ttv-content-editor,
  166. .ttv-control-panel.fullscreen .ttv-preview {
  167. height: calc(100vh - 250px);
  168. margin: 15px 0;
  169. font-size: 16px;
  170. }
  171. .ttv-header {
  172. margin-bottom: 20px;
  173. padding-bottom: 15px;
  174. border-bottom: 2px solid #eee;
  175. font-weight: bold;
  176. font-size: 16px;
  177. color: #444;
  178. display: flex;
  179. justify-content: space-between;
  180. align-items: center;
  181. }
  182. button.btn-warning {
  183. background: #f0ad4e;
  184. color: white;
  185. border: none;
  186. padding: 12px;
  187. border-radius: 6px;
  188. width: 100%;
  189. font-size: 14px;
  190. transition: all 0.2s ease;
  191. }
  192. button.btn-warning:hover {
  193. background: #ec971f;
  194. transform: translateY(-1px);
  195. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  196. }
  197. button.ttv-minimize {
  198. padding: 2px 8px;
  199. background: none;
  200. border: none;
  201. cursor: pointer;
  202. font-size: 16px;
  203. color: #666;
  204. transition: all 0.2s ease;
  205. }
  206. button.ttv-minimize:hover {
  207. color: #333;
  208. }
  209. `;
  210. document.head.appendChild(style);
  211.  
  212. // Tạo div thông báo
  213. const notification = document.createElement('div');
  214. notification.className = 'ttv-notification';
  215. document.body.appendChild(notification);
  216.  
  217. // Hiển thị thông báo
  218. function showNotification(message, isError = false) {
  219. notification.textContent = message;
  220. notification.className = 'ttv-notification' + (isError ? ' ttv-error' : '');
  221. notification.style.display = 'block';
  222. setTimeout(() => {
  223. notification.style.display = 'none';
  224. }, 3000);
  225. }
  226.  
  227. // Parse chapters function update
  228. function parseChapters(content) {
  229. const lines = content.split('\n');
  230. const chapters = [];
  231. let currentChapter = {
  232. title: '',
  233. name: '',
  234. content: []
  235. };
  236.  
  237. const chapterPattern = /^\s*Chương\s+\d+:/;
  238. let previousChapterTitle = '';
  239.  
  240. // Find chapters with clear markers
  241. for (let i = 0; i < lines.length; i++) {
  242. const line = lines[i].trim();
  243.  
  244. if (chapterPattern.test(line)) {
  245. // Check if it's a duplicate header
  246. if (line !== previousChapterTitle) {
  247. // Log for debug
  248. console.log(`Tìm thy chương mi: ${line}`);
  249.  
  250. // If there was a previous chapter, save it
  251. if (currentChapter.title && currentChapter.content.length > 0) {
  252. chapters.push({...currentChapter});
  253. console.log(`Đã lưu chương: ${currentChapter.title}\nTên: ${currentChapter.name}\nS dòng: ${currentChapter.content.length}`);
  254. currentChapter = {title: '', name: '', content: []};
  255. }
  256.  
  257. // Save current chapter title
  258. currentChapter.title = line;
  259. // Get chapter name after :
  260. let name = line.split(':')[1]?.trim() || '';
  261. // Remove punctuation from start and end of chapter name
  262. name = name.replace(/^[.,;'"]+|[.,;'"]+$/g, '').trim();
  263. currentChapter.name = name;
  264. previousChapterTitle = line;
  265.  
  266. console.log(`Đang x lý chương mi:\nTiêu đề: ${line}\nTên chương: ${name}`);
  267. } else {
  268. console.log(`B qua tiêu đề trùng lp: ${line}`);
  269. }
  270. } else {
  271. // Save all non-chapter-title content
  272. if (line) { // Only add non-empty lines
  273. if (currentChapter.title) {
  274. currentChapter.content.push(line);
  275. }
  276. }
  277. }
  278. }
  279.  
  280. // Add the last chapter if exists
  281. if (currentChapter.title && currentChapter.content.length > 0) {
  282. chapters.push({...currentChapter});
  283. }
  284.  
  285. // Display chapters and auto fill forms
  286. if (chapters.length > 0) {
  287. displayChapters(chapters);
  288. // Auto fill forms
  289. setTimeout(() => {
  290. try {
  291. transferContent();
  292. } catch (error) {
  293. console.error('Lỗi khi tự động điền form:', error);
  294. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  295. }
  296. }, 1000);
  297. }
  298.  
  299. console.log(`Tng s chương tìm thy: ${chapters.length}`);
  300. return chapters;
  301. }
  302.  
  303. // Thêm function createChapterHTML
  304. function createChapterHTML(chapNum) {
  305. const chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
  306. const chap_vol_name = jQuery('.chap_vol_name').val() || '';
  307. return `
  308. <div data-gen="MK_GEN" id="COUNT_CHAP_${chapNum}_MK">
  309. <div class="col-xs-12 form-group"></div>
  310. <div class="form-group">
  311. <label class="col-sm-2" for="chap_stt">STT</label>
  312. <div class="col-sm-8">
  313. <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"/>
  314. </div>
  315. </div>
  316. <div class="form-group">
  317. <label class="col-sm-2" for="chap_number">Chương thứ..</label>
  318. <div class="col-sm-8">
  319. <input value="${dăngnhanhTTV.STATE.CHAP_SERIAL}" required class="form-control" name="chap_number[${chapNum}]" placeholder="Chương thứ.. (1,2,3..)" type="text"/>
  320. </div>
  321. </div>
  322. <div class="form-group">
  323. <label class="col-sm-2" for="chap_name">Quyn số</label>
  324. <div class="col-sm-8">
  325. <input class="form-control" name="vol[${chapNum}]" placeholder="Quyển số" type="number" value="${chap_vol}" required/>
  326. </div>
  327. </div>
  328. <div class="form-group">
  329. <label class="col-sm-2" for="chap_name">Tên quyn</label>
  330. <div class="col-sm-8">
  331. <input class="form-control chap_vol_name" name="vol_name[${chapNum}]" placeholder="Tên quyển" type="text" value="${chap_vol_name}" />
  332. </div>
  333. </div>
  334. <div class="form-group">
  335. <label class="col-sm-2" for="chap_name">Tên chương</label>
  336. <div class="col-sm-8">
  337. <input required class="form-control" name="chap_name[${chapNum}]" placeholder="Tên chương" type="text"/>
  338. </div>
  339. </div>
  340. <div class="form-group">
  341. <label class="col-sm-2" for="introduce">Ni dung</label>
  342. <div class="col-sm-8">
  343. <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>
  344. <div class="chapter-character-count"></div>
  345. </div>
  346. </div>
  347. <div class="form-group">
  348. <label class="col-sm-2" for="adv">Qung cáo</label>
  349. <div class="col-sm-8">
  350. <textarea maxlength="1000" class="form-control" name="adv[${chapNum}]" placeholder="Quảng cáo" type="text"></textarea>
  351. </div>
  352. </div>
  353. </div>`;
  354. }
  355.  
  356. // Add state management
  357. const dăngnhanhTTV = {
  358. STATE: {
  359. CHAP_NUMBER: 1,
  360. CHAP_STT: 1,
  361. CHAP_SERIAL: 1,
  362. CHAP_NUMBER_ORIGINAL: 1,
  363. CHAP_STT_ORIGINAL: 1,
  364. CHAP_SERIAL_ORIGINAL: 1
  365. },
  366. initializeChapterValues: function() {
  367. try {
  368. const chap_number = parseInt(jQuery('#chap_number').val());
  369. let chap_stt = parseInt(jQuery('.chap_stt1').val());
  370. let chap_serial = parseInt(jQuery('.chap_serial').val());
  371.  
  372. if (parseInt(jQuery('#chap_stt').val()) > chap_stt) {
  373. chap_stt = parseInt(jQuery('#chap_stt').val());
  374. }
  375. if (parseInt(jQuery('#chap_serial').val()) > chap_serial) {
  376. chap_serial = parseInt(jQuery('#chap_serial').val());
  377. }
  378.  
  379. this.STATE.CHAP_NUMBER = this.STATE.CHAP_NUMBER_ORIGINAL = chap_number || 1;
  380. this.STATE.CHAP_STT = this.STATE.CHAP_STT_ORIGINAL = chap_stt || 1;
  381. this.STATE.CHAP_SERIAL = this.STATE.CHAP_SERIAL_ORIGINAL = chap_serial || 1;
  382. } catch (e) {
  383. console.error("Error initializing chapter values:", e);
  384. }
  385. }
  386. };
  387.  
  388. // Update transferContent function to use createChapterHTML
  389. async function transferContent() {
  390. try {
  391. console.log("Bắt đầu quá trình chuyển nội dung");
  392. // Get all chapters
  393. const chapterItems = document.querySelectorAll('.ttv-chapter-item');
  394. if (!chapterItems.length) {
  395. throw new Error('Không tìm thấy chương nào để điền vào form');
  396. }
  397. console.log(`Tìm thy ${chapterItems.length} chương để đin vào form`);
  398.  
  399. // Find the form container
  400. const form = document.querySelector('form[name="postChapForm"]');
  401. if (!form) {
  402. throw new Error('Không tìm thấy form đăng chương');
  403. }
  404. console.log("Tìm thấy form đăng chương");
  405.  
  406. // Get and update state values
  407. let chap_vol = 1;
  408. let chap_vol_name = '';
  409. try {
  410. chap_vol = parseInt(jQuery('.chap_vol').val()) || 1;
  411. chap_vol_name = jQuery('.chap_vol_name').val() || '';
  412. console.log(`Đã ly thông tin quyn: s ${chap_vol}, tên: ${chap_vol_name}`);
  413. } catch (e) {
  414. console.warn("Không thể lấy thông tin quyển, sử dụng giá trị mặc định", e);
  415. }
  416.  
  417. // Tìm STT chương và số thứ tự cao nhất trong form hiện tại
  418. let maxChapStt = 0;
  419. let maxChapSerial = 0;
  420. try {
  421. // Tìm tất cả các form chương hiện có
  422. const existingForms = form.querySelectorAll('[id^="COUNT_CHAP_"]');
  423. console.log(`Tìm thy ${existingForms.length} form chương hin có`);
  424. // Tìm STT và số thứ tự cao nhất
  425. existingForms.forEach(formElem => {
  426. try {
  427. // Lấy index từ ID của form, ví dụ: COUNT_CHAP_1_MK -> 1
  428. const formIdMatch = formElem.id.match(/COUNT_CHAP_(\d+)_MK/);
  429. if (formIdMatch && formIdMatch[1]) {
  430. const formIndex = parseInt(formIdMatch[1]);
  431. // Lấy giá trị STT
  432. const sttInput = formElem.querySelector(`input[name="chap_stt[${formIndex}]"]`);
  433. if (sttInput && sttInput.value && !isNaN(parseInt(sttInput.value))) {
  434. const sttVal = parseInt(sttInput.value);
  435. if (sttVal > maxChapStt) {
  436. maxChapStt = sttVal;
  437. }
  438. }
  439. // Lấy giá trị số thứ tự
  440. const serialInput = formElem.querySelector(`input[name="chap_number[${formIndex}]"]`);
  441. if (serialInput && serialInput.value && !isNaN(parseInt(serialInput.value))) {
  442. const serialVal = parseInt(serialInput.value);
  443. if (serialVal > maxChapSerial) {
  444. maxChapSerial = serialVal;
  445. }
  446. }
  447. }
  448. } catch (formError) {
  449. console.warn("Lỗi khi đọc giá trị từ form:", formError);
  450. }
  451. });
  452. console.log(`STT chương cao nht tìm thy: ${maxChapStt}, S th t cao nht: ${maxChapSerial}`);
  453. // Cập nhật state với giá trị cao nhất tìm được
  454. if (maxChapStt > 0) {
  455. dăngnhanhTTV.STATE.CHAP_STT = maxChapStt;
  456. }
  457. if (maxChapSerial > 0) {
  458. dăngnhanhTTV.STATE.CHAP_SERIAL = maxChapSerial;
  459. }
  460. } catch (e) {
  461. console.warn("Không thể tìm STT và số thứ tự cao nhất, sử dụng giá trị mặc định", e);
  462. }
  463. // Đếm số form chương hiện có để xác định index bắt đầu
  464. const existingFormCount = form.querySelectorAll('[id^="COUNT_CHAP_"]').length;
  465. console.log(`S form chương hin có: ${existingFormCount}`);
  466.  
  467. console.log(`Bt đầu to ${chapterItems.length} form chương mi t index ${existingFormCount + 1}`);
  468.  
  469. // Tạo form cho mỗi chương
  470. for (let i = 0; i < chapterItems.length; i++) {
  471. const formIndex = existingFormCount + i + 1;
  472. console.log(`Đang to form cho chương ${formIndex}`);
  473.  
  474. try {
  475. // Tăng các giá trị STT và số thứ tự cho chương mới
  476. dăngnhanhTTV.STATE.CHAP_STT++;
  477. dăngnhanhTTV.STATE.CHAP_SERIAL++;
  478. console.log(`S dng STT: ${dăngnhanhTTV.STATE.CHAP_STT}, S th tự: ${dăngnhanhTTV.STATE.CHAP_SERIAL}`);
  479. // Tạo HTML cho form chương mới
  480. const chapterHTML = createChapterHTML(formIndex);
  481. const tempDiv = document.createElement('div');
  482. tempDiv.innerHTML = chapterHTML;
  483. const newFormElement = tempDiv.firstElementChild;
  484. if (!newFormElement) {
  485. throw new Error(`Không th to element form cho chương ${formIndex}`);
  486. }
  487. form.appendChild(newFormElement);
  488. console.log(`Đã thêm form HTML cho chương ${formIndex}`);
  489. // Lấy thông tin chương
  490. const chapterItem = chapterItems[i];
  491. if (!chapterItem) {
  492. throw new Error(`Không tìm thy d liu cho chương ${formIndex}`);
  493. }
  494. const titleElement = chapterItem.querySelector('.chapter-title');
  495. const nameElement = chapterItem.querySelector('.chapter-name');
  496. if (!titleElement || !nameElement) {
  497. throw new Error(`Thiếu thông tin tiêu đề hoc tên cho chương ${formIndex}`);
  498. }
  499. const chapterTitle = titleElement.textContent;
  500. const chapterName = nameElement.textContent.replace('Tên chương: ', '');
  501. const chapterNumberMatch = chapterTitle.match(/Chương\s+(\d+)/);
  502. const chapterNumber = chapterNumberMatch ? chapterNumberMatch[1] : dăngnhanhTTV.STATE.CHAP_SERIAL.toString();
  503. console.log(`Thông tin chương ${formIndex}: Tiêu đề: ${chapterTitle}, Tên: ${chapterName}, Số: ${chapterNumber}`);
  504.  
  505. // Lấy các trường form
  506. const formFields = {
  507. chapterName: form.querySelector(`input[name="chap_name[${formIndex}]"]`),
  508. content: form.querySelector(`textarea[name="introduce[${formIndex}]"]`),
  509. chapterNumber: form.querySelector(`input[name="chap_number[${formIndex}]"]`),
  510. chapterOrder: form.querySelector(`input[name="chap_stt[${formIndex}]"]`),
  511. volume: form.querySelector(`input[name="vol[${formIndex}]"]`),
  512. volumeName: form.querySelector(`input[name="vol_name[${formIndex}]"]`),
  513. advertisement: form.querySelector(`textarea[name="adv[${formIndex}]"]`)
  514. };
  515.  
  516. // Kiểm tra xem có thiếu trường nào không
  517. const missingFields = [];
  518. for (const [fieldName, element] of Object.entries(formFields)) {
  519. if (!element) {
  520. missingFields.push(fieldName);
  521. }
  522. }
  523.  
  524. if (missingFields.length > 0) {
  525. throw new Error(`Thiếu các trường ${missingFields.join(', ')} trong form ${formIndex}`);
  526. }
  527.  
  528. // Điền dữ liệu vào form
  529. formFields.chapterName.value = chapterName;
  530. formFields.chapterNumber.value = dăngnhanhTTV.STATE.CHAP_SERIAL.toString();
  531. formFields.chapterOrder.value = dăngnhanhTTV.STATE.CHAP_STT.toString();
  532. formFields.volume.value = chap_vol;
  533. formFields.volumeName.value = chap_vol_name;
  534. // Kiểm tra và gán nội dung
  535. if (chapterItem._content) {
  536. formFields.content.value = chapterItem._content;
  537. } else {
  538. console.warn(`Chương ${formIndex} không có ni dung`);
  539. formFields.content.value = '';
  540. }
  541. formFields.advertisement.value = '';
  542.  
  543. console.log(`Đã đin form cho chương ${formIndex}: Tên: ${chapterName}, STT: ${dăngnhanhTTV.STATE.CHAP_STT}, S th tự: ${dăngnhanhTTV.STATE.CHAP_SERIAL}, Độ dài ni dung: ${formFields.content.value.length} ký tự`);
  544.  
  545. // Kích hoạt sự kiện input để cập nhật số đếm ký tự
  546. try {
  547. const inputEvent = new Event('input', { bubbles: true });
  548. formFields.content.dispatchEvent(inputEvent);
  549. } catch (inputError) {
  550. console.warn(`Không th kích hot s kin input cho chương ${formIndex}:`, inputError);
  551. }
  552. } catch (chapterError) {
  553. console.error(`Li khi x lý chương ${formIndex}:`, chapterError);
  554. showNotification(`Li chương ${formIndex}: ${chapterError.message}`, true);
  555. // Tiếp tục với chương tiếp theo thay vì dừng toàn bộ quá trình
  556. }
  557. }
  558.  
  559. showNotification(`Đã t động đin ${chapterItems.length} chương vào form!`);
  560. console.log(`Đã hoàn thành vic đin ${chapterItems.length} chương vào form`);
  561.  
  562. } catch (error) {
  563. console.error('Lỗi khi điền form:', error);
  564. showNotification('Có lỗi xảy ra khi điền form: ' + error.message, true);
  565. }
  566. }
  567.  
  568. // Chọn chương để hiển thị
  569. function selectChapter(chapter, index) {
  570. const contentEditor = document.querySelector('.ttv-content-editor');
  571. contentEditor.value = chapter.title + '\n' + chapter.content.join('\n');
  572.  
  573. // Cập nhật số từ
  574. const wordCountSpan = document.querySelector('.ttv-word-count');
  575. if (wordCountSpan) {
  576. wordCountSpan.textContent = updateWordCount(contentEditor.value);
  577. }
  578.  
  579. // Highlight selected chapter
  580. const chapterItems = document.querySelectorAll('.ttv-chapter-item');
  581. chapterItems.forEach(item => item.classList.remove('selected'));
  582. chapterItems[index]?.classList.add('selected');
  583.  
  584. // Tự động điền form khi chọn chương
  585. try {
  586. transferContent();
  587. } catch (error) {
  588. console.error('Lỗi khi tự động điền form:', error);
  589. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  590. }
  591. }
  592.  
  593. // Hiển thị danh sách chương
  594. function displayChapters(chapters) {
  595. const chapterList = document.createElement('div');
  596. chapterList.className = 'ttv-chapter-list';
  597.  
  598. chapters.forEach((chapter, index) => {
  599. const chapterItem = document.createElement('div');
  600. chapterItem.className = 'ttv-chapter-item';
  601.  
  602. // Store chapter content
  603. chapterItem._content = chapter.title + '\n' + chapter.content.join('\n');
  604.  
  605. // Tạo các phần tử con với định dạng riêng
  606. const titleDiv = document.createElement('div');
  607. titleDiv.className = 'chapter-title';
  608. titleDiv.textContent = chapter.title;
  609.  
  610. const nameDiv = document.createElement('div');
  611. nameDiv.className = 'chapter-name';
  612. nameDiv.textContent = `Tên chương: ${chapter.name}`;
  613.  
  614. const statsDiv = document.createElement('div');
  615. statsDiv.className = 'chapter-stats';
  616. statsDiv.textContent = `${chapter.content.length} dòng`;
  617.  
  618. // Thêm các phần tử vào item
  619. chapterItem.appendChild(titleDiv);
  620. chapterItem.appendChild(nameDiv);
  621. chapterItem.appendChild(statsDiv);
  622.  
  623. chapterItem.onclick = () => selectChapter(chapter, index);
  624. chapterList.appendChild(chapterItem);
  625.  
  626. // Select first chapter by default
  627. if (index === 0) {
  628. chapterItem.classList.add('selected');
  629. }
  630. });
  631.  
  632. const existingList = document.querySelector('.ttv-chapter-list');
  633. if (existingList) {
  634. existingList.remove();
  635. }
  636.  
  637. const contentEditor = document.querySelector('.ttv-content-editor');
  638. contentEditor.parentNode.insertBefore(chapterList, contentEditor);
  639.  
  640. // Hiển thị thông báo về số chương tìm thấy
  641. showNotification(`Đã tìm thy ${chapters.length} chương và t động đin vào form.`);
  642.  
  643. // Tự động chọn chương đầu tiên
  644. if (chapters.length > 0) {
  645. selectChapter(chapters[0], 0);
  646. }
  647. }
  648.  
  649. // Đếm số từ và ký tự
  650. function updateWordCount(content) {
  651. const wordCount = content.trim().split(/\s+/).length;
  652. const charCount = content.length;
  653. return `${wordCount} t | ${charCount} ký tự`;
  654. }
  655.  
  656. // Chuyển đổi giữa chế độ soạn thảo và xem trước
  657. function togglePreview() {
  658. const contentEditor = document.querySelector('.ttv-content-editor');
  659. const preview = document.querySelector('.ttv-preview');
  660. const previewBtn = document.querySelector('.ttv-preview-btn');
  661.  
  662. if (contentEditor.style.display !== 'none') {
  663. contentEditor.style.display = 'none';
  664. preview.style.display = 'block';
  665. preview.innerHTML = contentEditor.value.replace(/\n/g, '<br>');
  666. previewBtn.textContent = 'Soạn thảo';
  667. } else {
  668. contentEditor.style.display = 'block';
  669. preview.style.display = 'none';
  670. previewBtn.textContent = 'Xem trước';
  671. }
  672. }
  673.  
  674. // Chuyển đổi chế độ toàn màn hình
  675. function toggleFullscreen() {
  676. const panel = document.querySelector('.ttv-control-panel');
  677. const fullscreenBtn = document.querySelector('.ttv-fullscreen-btn');
  678. panel.classList.toggle('fullscreen');
  679. fullscreenBtn.textContent = panel.classList.contains('fullscreen') ? 'Thu nhỏ' : 'Toàn màn hình';
  680. }
  681.  
  682. // Thêm panel điều khiển
  683. function addControlPanel() {
  684. // Tạo panel
  685. const panel = document.createElement('div');
  686. panel.className = 'ttv-control-panel';
  687.  
  688. // Thêm header
  689. const header = document.createElement('div');
  690. header.className = 'ttv-header';
  691. header.innerHTML = `
  692. <div>Son Tho Ni Dung</div>
  693. <div class="ttv-toolbar">
  694. <button class="ttv-preview-btn" onclick="togglePreview()">Xem trước</button>
  695. <button class="ttv-fullscreen-btn" onclick="toggleFullscreen()">Toàn màn hình</button>
  696. <button class="ttv-minimize">−</button>
  697. </div>
  698. `;
  699.  
  700. const buttonGroup = document.createElement('div');
  701. buttonGroup.className = 'ttv-button-group';
  702.  
  703. // Khung soạn thảo nội dung
  704. const contentEditorLabel = document.createElement('div');
  705. contentEditorLabel.className = 'ttv-heading';
  706. contentEditorLabel.innerHTML = `
  707. Ni dung chương:
  708. <span class="ttv-word-count">0 t | 0 ký tự</span>
  709. `;
  710.  
  711. const contentEditor = document.createElement('textarea');
  712. contentEditor.className = 'ttv-content-editor';
  713. contentEditor.placeholder = 'Nhập hoặc dán nội dung chương vào đây...';
  714.  
  715. // Khung xem trước
  716. const preview = document.createElement('div');
  717. preview.className = 'ttv-preview';
  718.  
  719. // Cập nhật số từ khi nhập nội dung
  720. contentEditor.oninput = () => {
  721. const wordCountSpan = document.querySelector('.ttv-word-count');
  722. if (wordCountSpan) {
  723. wordCountSpan.textContent = updateWordCount(contentEditor.value);
  724. }
  725. };
  726.  
  727. // Xử lý khi paste nội dung
  728. contentEditor.onpaste = (e) => {
  729. // Cho phép paste hoàn tất
  730. setTimeout(() => {
  731. const content = contentEditor.value;
  732. const chapters = parseChapters(content);
  733. if (chapters.length > 0) {
  734. displayChapters(chapters);
  735. // Tự động điền form sau khi hiển thị danh sách chương
  736. setTimeout(() => {
  737. try {
  738. transferContent();
  739. } catch (error) {
  740. console.error('Lỗi khi tự động điền form:', error);
  741. showNotification('Có lỗi xảy ra khi tự động điền form!', true);
  742. }
  743. }, 1000); // Đợi 1s để đảm bảo UI đã được cập nhật hoàn toàn
  744. }
  745. }, 0);
  746. };
  747.  
  748. // Nút chuyển nội dung
  749. const transferBtn = document.createElement('button');
  750. transferBtn.type = 'button';
  751. transferBtn.className = 'btn btn-warning';
  752. transferBtn.innerHTML = '<span>Chuyển nội dung sang form</span>';
  753. transferBtn.onclick = transferContent;
  754.  
  755. // Thêm các phần tử vào panel
  756. panel.appendChild(header);
  757. buttonGroup.appendChild(contentEditorLabel);
  758. buttonGroup.appendChild(contentEditor);
  759. buttonGroup.appendChild(preview);
  760. buttonGroup.appendChild(transferBtn);
  761. panel.appendChild(buttonGroup);
  762.  
  763. document.body.appendChild(panel);
  764.  
  765. // Thêm xử lý sự kiện cho các nút trong toolbar
  766. const minimizeBtn = panel.querySelector('.ttv-minimize');
  767. minimizeBtn.onclick = () => {
  768. panel.classList.toggle('minimized');
  769. minimizeBtn.innerHTML = panel.classList.contains('minimized') ? '+' : '−';
  770. };
  771.  
  772. window.togglePreview = togglePreview;
  773. window.toggleFullscreen = toggleFullscreen;
  774. }
  775.  
  776. // Initialize chapter values when page loads
  777. window.addEventListener('load', function() {
  778. dăngnhanhTTV.initializeChapterValues();
  779. addControlPanel();
  780. });
  781. })();