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