您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mengisi survei lingkungan belajar GTK Dikdasmen dengan pengaturan per soal dan pola rentang, fokus pada halaman yang aktif. Dilengkapi logging debugging.
// ==UserScript== // @name Isikan AKu Auto-Fill Survey Lingkungan Belajar (GTK Dikdasmen) // @namespace http://tampermonkey.net/ // @version 3.0 // @description Mengisi survei lingkungan belajar GTK Dikdasmen dengan pengaturan per soal dan pola rentang, fokus pada halaman yang aktif. Dilengkapi logging debugging. // @author ChatGPT // @match https://gtk.dikdasmen.go.id/* // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; // Objek untuk menyimpan informasi detail setiap pertanyaan (semua 88 soal) const allQuestionsData = {}; // Objek untuk menyimpan konfigurasi jawaban yang diinginkan pengguna const userConfiguredAnswers = {}; // Variabel untuk mengontrol inisialisasi dan polling let initializationAttempts = 0; const MAX_INITIALIZATION_ATTEMPTS = 30; // Coba hingga 30 kali (15 detik jika interval 500ms) let initializationIntervalId = null; /** * Membaca teks pertanyaan dari DOM. * Disesuaikan untuk struktur yang Anda berikan, termasuk sub-pertanyaan dan bagian utama. */ function getQuestionText(element, soalNumber) { let subQuestionText = ''; let mainSectionTitle = ''; // 1. Get the immediate question text (e.g., "a.", "Seberapa mudah...") if (element.tagName === 'INPUT' && element.type === 'radio') { // Check for structure: <tr><td>a.</td><td>Question Text</td><td>...radio buttons...</td></tr> const tdElement = element.closest('td'); if (tdElement && tdElement.previousElementSibling) { // This gets 'a.' or 'b.' text subQuestionText = tdElement.previousElementSibling.textContent.trim(); // Get the actual question text (second td) if it exists const questionTd = tdElement.previousElementSibling.previousElementSibling; if (questionTd && questionTd.tagName === 'TD') { // This is for the structure where 'No' is the first TD and 'Pertanyaan' is the second subQuestionText = questionTd.textContent.trim(); } // If the question is in a 'radio-wrapper' not inside a table with <td>No</td><td>Pertanyaan</td> // And if 'subQuestionText' is still just a letter like 'a.' or empty, try finding the label associated if (subQuestionText.length <= 2 && element.nextElementSibling && element.nextElementSibling.tagName === 'LABEL') { subQuestionText = element.nextElementSibling.textContent.trim(); // If it's a Likert scale where labels are the options, we might not get the full question text here easily. // The H5 title will be crucial. if (subQuestionText.includes('Sulit') || subQuestionText.includes('Setuju') || subQuestionText.includes('Bermanfaat') || subQuestionText.includes('Kurang')) { subQuestionText = `[Skala Likert]`; // Generic marker for these types } } } } else if (element.tagName === 'TEXTAREA') { const labelElement = element.previousElementSibling; if (labelElement && labelElement.tagName === 'LABEL') { const strongTag = labelElement.querySelector('strong'); subQuestionText = strongTag ? strongTag.textContent.trim() : labelElement.textContent.trim(); } } // 2. Get the main section title (H5) if applicable const parentFormPage = element.closest('.form-page'); if (parentFormPage) { const h5Title = parentFormPage.querySelector('h5.font-weight-bold.mb-3'); if (h5Title) { // Remove leading number and dot (e.g., "1. ") from the H5 text mainSectionTitle = h5Title.textContent.replace(/^\d+\.\s*/, '').trim(); } } // 3. Combine them intelligently let finalQuestionText = ''; if (mainSectionTitle && subQuestionText && subQuestionText !== `[Skala Likert]`) { // Avoid redundancy if subQuestionText already starts with the H5 title's context if (subQuestionText.startsWith(mainSectionTitle)) { finalQuestionText = subQuestionText; } else { finalQuestionText = `${mainSectionTitle} - ${subQuestionText}`; } } else if (mainSectionTitle && subQuestionText === `[Skala Likert]`) { // For Likert scale questions, use the H5 title as primary text finalQuestionText = `${mainSectionTitle} - Soal Skala`; } else if (subQuestionText) { finalQuestionText = subQuestionText; // Use the most immediate question text } else if (mainSectionTitle) { finalQuestionText = mainSectionTitle; // Use H5 title as fallback } else { finalQuestionText = `Soal #${soalNumber}`; // Generic fallback } // Trim and ensure it's not empty, if so, default to generic. finalQuestionText = finalQuestionText.trim(); if (!finalQuestionText) { finalQuestionText = `Soal #${soalNumber} (Teks tidak terdeteksi)`; } return finalQuestionText; } /** * Mengumpulkan informasi semua pertanyaan di seluruh DOM. * Mengisi `allQuestionsData` dengan detail setiap soal. */ function collectAllQuestionsInfo() { console.log("Collecting all questions info (initial scan)..."); const processedRadioGroups = new Set(); const foundSoalNumbers = new Set(); let questionsCount = 0; // Collect Radio Button Questions document.querySelectorAll('input[type="radio"].soal-radio').forEach(input => { const soalNumber = input.dataset.nosoal; const radioGroupName = input.name; // Important: Filter out hidden inputs associated with radio groups, they also have class 'soal-radio' // The ones we want have type="radio". The hidden ones have no type or type="hidden". if (input.type !== 'radio') { return; // Skip hidden inputs with 'soal-radio' class } if (soalNumber && radioGroupName && !processedRadioGroups.has(radioGroupName)) { processedRadioGroups.add(radioGroupName); foundSoalNumbers.add(soalNumber); const radiosInGroup = document.querySelectorAll(`input[name="${radioGroupName}"][type="radio"]`); // Ensure only radio inputs // If this is a question with letter numbering (like 'a.', 'b.', 'c.') // it might have a generic question text, we need to find the specific question text from the <td>. // Re-evaluate the question text here to be robust. allQuestionsData[soalNumber] = { type: "radio", name: radioGroupName, id: input.id, numOptions: radiosInGroup.length, questionText: getQuestionText(input, soalNumber) }; questionsCount++; } }); // Collect Textarea Questions document.querySelectorAll('textarea.soal-radio').forEach(textarea => { const soalNumber = textarea.dataset.nosoal; if (soalNumber && !foundSoalNumbers.has(soalNumber)) { foundSoalNumbers.add(soalNumber); allQuestionsData[soalNumber] = { type: "textarea", name: textarea.name, id: textarea.id, questionText: getQuestionText(textarea, soalNumber) }; questionsCount++; } }); // Sort questionsData by soalNumber keys const sortedKeys = Object.keys(allQuestionsData).sort((a, b) => parseInt(a) - parseInt(b)); const sortedQuestionsDataTemp = {}; sortedKeys.forEach(key => { sortedQuestionsDataTemp[key] = allQuestionsData[key]; }); Object.assign(allQuestionsData, sortedQuestionsDataTemp); console.log(`Initial scan complete. Total unique questions found: ${questionsCount}. Details:`, allQuestionsData); } /** * Membaca jawaban yang sudah ada di form dan menyimpannya ke userConfiguredAnswers. */ function readCurrentAnswersFromForm() { console.log("Reading current answers from form..."); // Iterate only over questions that are currently in the DOM const currentSoalElements = document.querySelectorAll('input[type="radio"].soal-radio, textarea.soal-radio'); currentSoalElements.forEach(el => { const soalNumber = el.dataset.nosoal; const qData = allQuestionsData[soalNumber]; // Skip hidden inputs. They also have soal-radio but aren't interactable. if (el.tagName === 'INPUT' && el.type !== 'radio') { return; } if (!qData) { console.warn(`[Read Current] Soal ${soalNumber} is in DOM but not in allQuestionsData. Skipping.`); return; } if (qData.type === "radio") { const selectedRadio = document.querySelector(`input[name="${qData.name}"][type="radio"]:checked`); // Explicitly target type="radio" if (selectedRadio) { const radiosInGroup = Array.from(document.querySelectorAll(`input[name="${qData.name}"][type="radio"]`)); const selectedIndex = radiosInGroup.indexOf(selectedRadio); if (selectedIndex !== -1) { userConfiguredAnswers[soalNumber] = selectedIndex; // Store 0-indexed } } else { delete userConfiguredAnswers[soalNumber]; } } else if (qData.type === "textarea") { const textareaEl = document.getElementById(qData.id); if (textareaEl && textareaEl.value) { userConfiguredAnswers[soalNumber] = textareaEl.value; } else { delete userConfiguredAnswers[soalNumber]; } } }); console.log('Current answers read from form:', userConfiguredAnswers); } /** * Fungsi untuk menerapkan jawaban yang sudah dikonfigurasi ke elemen form yang terlihat. */ function applyConfiguredAnswersToVisibleQuestions() { console.log("Applying configured answers to visible questions (actual form interaction)..."); let questionsAnswered = 0; let questionsSkipped = 0; const visibleFormPage = document.querySelector('.form-page[style*="block"]'); if (!visibleFormPage) { alert("Tidak ada halaman form yang terlihat. Pastikan Anda berada di halaman survei yang aktif."); return; } const visibleElements = visibleFormPage.querySelectorAll('input[type="radio"].soal-radio, textarea.soal-radio'); if (visibleElements.length === 0) { alert("Tidak ditemukan pertanyaan di halaman yang terlihat. Pastikan halaman sudah termuat sepenuhnya."); return; } visibleElements.forEach(el => { // Skip hidden inputs. They also have soal-radio but aren't interactable. if (el.tagName === 'INPUT' && el.type !== 'radio') { return; } const soalNumber = el.dataset.nosoal; const qData = allQuestionsData[soalNumber]; if (!qData) { console.warn(`[Apply Visible] Informasi untuk Soal ${soalNumber} tidak ditemukan di allQuestionsData. Skipping.`); questionsSkipped++; return; } const desiredAnswer = userConfiguredAnswers[soalNumber]; if (typeof desiredAnswer === 'undefined' || (qData.type === 'textarea' && desiredAnswer === '')) { questionsSkipped++; return; } if (qData.type === "radio") { const radiosInGroup = document.querySelectorAll(`input[name="${qData.name}"][type="radio"]`); if (radiosInGroup.length > desiredAnswer) { const targetRadio = radiosInGroup[desiredAnswer]; if (targetRadio) { if (!targetRadio.checked) { targetRadio.click(); // Simulate user click questionsAnswered++; console.log(`[Applied] Soal ${soalNumber}: Klik opsi ${desiredAnswer + 1}`); } else { questionsSkipped++; console.log(`[Skipped] Soal ${soalNumber}: Opsi ${desiredAnswer + 1} sudah terpilih.`); } } else { console.warn(`[SKIPPED] Opsi ke-${desiredAnswer + 1} tidak ditemukan untuk Soal ${soalNumber} (radio elemen).`); questionsSkipped++; } } else { console.warn(`[SKIPPED] Soal ${soalNumber} (radio) hanya memiliki ${radiosInGroup.length} opsi, tidak cukup untuk memilih opsi ke-${desiredAnswer + 1}.`); questionsSkipped++; } } else if (qData.type === "textarea") { const textareaEl = document.getElementById(qData.id); if (textareaEl) { if (textareaEl.value !== desiredAnswer) { textareaEl.value = desiredAnswer; const event = new Event('input', { bubbles: true }); textareaEl.dispatchEvent(event); questionsAnswered++; console.log(`[Applied] Soal ${soalNumber}: Mengisi teks "${desiredAnswer}"`); } else { questionsSkipped++; console.log(`[Skipped] Soal ${soalNumber}: Teks sudah sama.`); } } else { console.warn(`[SKIPPED] Textarea dengan ID ${qData.id} (Soal ${soalNumber}) tidak ditemukan.`); questionsSkipped++; } } }); alert(`Selesai! ${questionsAnswered} pertanyaan di halaman ini telah diisi sesuai konfigurasi. ${questionsSkipped} pertanyaan dilewati (sudah terisi/tidak memenuhi kriteria/tidak memiliki konfigurasi).`); } /** * Menerapkan pola jawaban ke rentang soal yang ditentukan DALAM HALAMAN YANG TERLIHAT. */ function applyPatternToRange() { const startSoalInput = document.getElementById('start-soal-input'); const endSoalInput = document.getElementById('end-soal-input'); const patternInput = document.getElementById('pattern-input'); const startSoal = parseInt(startSoalInput.value); const endSoal = parseInt(endSoalInput.value); const patternString = patternInput.value.trim(); if (isNaN(startSoal) || isNaN(endSoal) || startSoal < 1 || endSoal < startSoal) { alert("Nomor soal awal dan akhir tidak valid. Pastikan ini adalah angka dan awal <= akhir."); return; } const pattern = patternString.split(',').map(s => { const num = parseInt(s.trim()); return isNaN(num) ? null : num - 1; // Convert to 0-indexed, or null if invalid }).filter(n => n !== null); // Filter out invalid numbers if (pattern.length === 0) { alert("Pola jawaban tidak valid. Masukkan angka yang dipisahkan koma (misal: 1,2,3)."); return; } console.log(`Applying pattern "${patternString}" to visible questions from ${startSoal} to ${endSoal}...`); let configuredCount = 0; const visibleFormPage = document.querySelector('.form-page[style*="block"]'); if (!visibleFormPage) { alert("Tidak ada halaman form yang terlihat untuk menerapkan pola."); return; } // Get visible radio question elements on the current page // Filter out hidden inputs that might have soal-radio class const visibleRadioElements = visibleFormPage.querySelectorAll('input[type="radio"].soal-radio[type="radio"]'); const visibleSoalNumbersOnPage = new Set(); visibleRadioElements.forEach(el => visibleSoalNumbersOnPage.add(el.dataset.nosoal)); // Filter these visible soal numbers by the user's defined range const targetSoalNumbers = Array.from(visibleSoalNumbersOnPage) .map(s => parseInt(s)) .filter(num => num >= startSoal && num <= endSoal) .sort((a, b) => a - b); if (targetSoalNumbers.length === 0) { alert("Tidak ada pertanyaan radio button yang terlihat di halaman ini dalam rentang yang ditentukan."); return; } targetSoalNumbers.forEach((numSoal, indexInFilteredList) => { const soalNumber = String(numSoal); const qData = allQuestionsData[soalNumber]; if (qData && qData.type === "radio") { const patternIndex = indexInFilteredList % pattern.length; // Use index in filtered list for pattern application const desiredOptionIndex = pattern[patternIndex]; if (desiredOptionIndex < qData.numOptions) { userConfiguredAnswers[soalNumber] = desiredOptionIndex; configuredCount++; console.log(` Soal ${soalNumber}: Pola -> Opsi ${desiredOptionIndex + 1}`); } else { console.warn(` Soal ${soalNumber} memiliki ${qData.numOptions} opsi, tidak dapat menerapkan opsi ke-${desiredOptionIndex + 1} dari pola.`); } } }); alert(`${configuredCount} jawaban radio button telah dikonfigurasi berdasarkan pola di halaman ini. Sekarang akan diterapkan ke form.`); updateControlPanel(); // Refresh UI panel to reflect changes applyConfiguredAnswersToVisibleQuestions(); // Apply the changes to the form immediately } // --- Pembuatan dan Pembaruan Antarmuka Pengguna (UI) --- function updateControlPanel() { console.log("Updating control panel UI..."); let panel = document.getElementById('survey-auto-fill-per-question-panel'); if (panel) { panel.remove(); } panel = document.createElement('div'); panel.id = 'survey-auto-fill-per-question-panel'; panel.style.cssText = ` position: fixed; top: 20px; right: 20px; background-color: rgba(255, 255, 255, 0.98); border: 1px solid #ddd; padding: 15px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 99999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; gap: 8px; max-height: 95vh; overflow-y: auto; width: 320px; `; const title = document.createElement('div'); title.innerHTML = ` <strong style="color: #333; font-size: 1.1em;">Isi Survei Otomatis</strong> <div style="font-size: 0.85em; color: #666; margin-top: 5px;"> *Konfigurasi ini akan disimpan untuk semua soal (88) dan tetap berlaku saat berpindah halaman. </div> <hr style="border: 0; border-top: 1px solid #eee; margin: 10px 0;"> `; panel.appendChild(title); // --- Bagian Pengaturan Pola Rentang --- const patternSection = document.createElement('div'); patternSection.style.cssText = ` border: 1px dashed #007bff; padding: 10px; border-radius: 5px; margin-bottom: 10px; background-color: #eaf6ff; color: #0056b3; `; const allSoalNumbers = Object.keys(allQuestionsData).map(s => parseInt(s)).sort((a,b) => a-b); const minSoalNum = allSoalNumbers.length > 0 ? allSoalNumbers[0] : 1; const maxSoalNum = allSoalNumbers.length > 0 ? allSoalNumbers[allSoalNumbers.length - 1] : 88; patternSection.innerHTML = ` <strong style="font-size: 1em;">Atur Pola Jawaban untuk Rentang Soal Radio Button yang Tampil:</strong> <div style="font-size: 0.8em; margin-top: 5px;"> Dari soal nomor: <input type="number" id="start-soal-input" min="1" value="${minSoalNum}" style="width: 50px; padding: 3px; margin: 0 2px;"> sampai <input type="number" id="end-soal-input" min="1" value="${maxSoalNum}" style="width: 50px; padding: 3px; margin: 0 2px;"> </div> <div style="font-size: 0.8em; margin-top: 5px;"> Pola jawaban (1-indexed, pisahkan koma): <input type="text" id="pattern-input" placeholder="contoh: 1,2,3" value="1" style="width: 100%; padding: 3px; margin-top: 5px;"> </div> <button id="apply-pattern-btn" style=" width: 100%; padding: 8px; margin-top: 10px; border: none; border-radius: 4px; background-color: #007bff; color: white; font-weight: bold; cursor: pointer; transition: background-color 0.2s; ">Terapkan Pola ke Soal Tampil</button> `; panel.appendChild(patternSection); // <--- INI PENTING: TAMBAHKAN DULU KE DOM! // Pastikan elemen input ada setelah ditambahkan ke DOM! const applyPatternBtn = panel.querySelector('#apply-pattern-btn'); if (applyPatternBtn) { applyPatternBtn.onclick = applyPatternToRange; applyPatternBtn.onmouseover = () => applyPatternBtn.style.backgroundColor = '#0056b3'; applyPatternBtn.onmouseout = () => applyPatternBtn.style.backgroundColor = '#007bff'; } else { // Ini seharusnya tidak terjadi lagi jika panel.appendChild(patternSection) sudah benar console.error("Critical Error: Input/button for pattern range not found after panel creation. Please check script."); } panel.appendChild(document.createElement('hr')).style.cssText = "border: 0; border-top: 1px solid #eee; margin: 10px 0;"; // --- Bagian Pengaturan Per Soal Individual di Halaman Aktif --- const visibleFormPage = document.querySelector('.form-page[style*="block"]'); if (!visibleFormPage) { const noQuestionsMsg = document.createElement('p'); noQuestionsMsg.style.cssText = 'color: #999; font-size: 0.9em; text-align: center;'; noQuestionsMsg.textContent = 'Tidak ada pertanyaan yang terlihat di halaman ini.'; panel.appendChild(noQuestionsMsg); } else { // Only select visible, actual input/textarea elements, NOT hidden inputs const visibleQuestionsElements = visibleFormPage.querySelectorAll('input[type="radio"].soal-radio[type="radio"], textarea.soal-radio'); const visibleSoalNumbers = new Set(); visibleQuestionsElements.forEach(el => visibleSoalNumbers.add(el.dataset.nosoal)); const sortedVisibleSoalNumbers = Array.from(visibleSoalNumbers).sort((a, b) => parseInt(a) - parseInt(b)); if (sortedVisibleSoalNumbers.length === 0) { const noQuestionsMsg = document.createElement('p'); noQuestionsMsg.style.cssText = 'color: #999; font-size: 0.9em; text-align: center;'; noQuestionsMsg.textContent = 'Tidak ada pertanyaan terdeteksi di bagian form yang aktif.'; panel.appendChild(noQuestionsMsg); } else { sortedVisibleSoalNumbers.forEach(soalNumber => { const qData = allQuestionsData[soalNumber]; if (!qData) { console.warn(`Soal ${soalNumber} terlihat tapi tidak ada di allQuestionsData.`); return; } const inputRow = document.createElement('div'); inputRow.style.cssText = 'display: flex; flex-direction: column; gap: 3px;'; const questionTextDisplay = document.createElement('div'); questionTextDisplay.style.cssText = 'font-weight: bold; font-size: 0.9em; color: #555;'; const maxTextLength = 50; let displayQuestionText = qData.questionText; if (displayQuestionText.length > maxTextLength) { displayQuestionText = displayQuestionText.substring(0, maxTextLength) + '...'; } questionTextDisplay.textContent = `Soal ${soalNumber}: ${displayQuestionText}`; inputRow.appendChild(questionTextDisplay); let inputElement; if (qData.type === "radio") { inputElement = document.createElement('input'); inputElement.type = 'number'; inputElement.min = 1; inputElement.max = qData.numOptions; inputElement.placeholder = `1-${qData.numOptions}`; inputElement.style.cssText = ` width: 100%; padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; text-align: center; `; if (typeof userConfiguredAnswers[soalNumber] !== 'undefined') { inputElement.value = userConfiguredAnswers[soalNumber] + 1; // Convert to 1-indexed } else { inputElement.value = ''; } inputElement.onchange = (event) => { const val = parseInt(event.target.value); if (!isNaN(val) && val >= 1 && val <= qData.numOptions) { userConfiguredAnswers[soalNumber] = val - 1; // Store 0-indexed console.log(`Konfigurasi: Soal ${soalNumber} = Opsi ${val}`); } else { delete userConfiguredAnswers[soalNumber]; console.log(`Konfigurasi untuk Soal ${soalNumber} dihapus (input tidak valid).`); event.target.value = ''; } }; } else if (qData.type === "textarea") { inputElement = document.createElement('textarea'); inputElement.rows = 2; inputElement.placeholder = "Ketik jawaban di sini..."; inputElement.style.cssText = ` width: 100%; padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; resize: vertical; `; if (typeof userConfiguredAnswers[soalNumber] !== 'undefined') { inputElement.value = userConfiguredAnswers[soalNumber]; } else { inputElement.value = ''; } inputElement.oninput = (event) => { userConfiguredAnswers[soalNumber] = event.target.value.trim(); console.log(`Konfigurasi: Soal ${soalNumber} = "${userConfiguredAnswers[soalNumber]}"`); }; } if (inputElement) { inputRow.appendChild(inputElement); panel.appendChild(inputRow); } }); // Tombol untuk menerapkan semua jawaban yang dikonfigurasi di panel ini const applyVisibleButton = document.createElement('button'); applyVisibleButton.textContent = 'Terapkan Jawaban ke Halaman Ini'; applyVisibleButton.style.cssText = ` padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; background-color: #28a745; color: white; font-size: 14px; font-weight: bold; margin-top: 15px; transition: background-color 0.2s ease, transform 0.1s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.1); `; applyVisibleButton.onmouseover = () => applyVisibleButton.style.backgroundColor = '#218838'; applyVisibleButton.onmousedown = () => applyVisibleButton.style.transform = 'translateY(1px)'; applyVisibleButton.onmouseup = () => applyVisibleButton.style.transform = 'translateY(0)'; applyVisibleButton.onmouseout = () => { applyVisibleButton.style.backgroundColor = '#28a745'; applyVisibleButton.style.transform = 'translateY(0)'; }; applyVisibleButton.onclick = applyConfiguredAnswersToVisibleQuestions; panel.appendChild(applyVisibleButton); } } document.body.appendChild(panel); console.log("Control panel appended to body."); } // --- Inisialisasi Script --- let observer; let debounceTimer; function initializeScript() { console.log(`Attempting to initialize script (Attempt ${initializationAttempts + 1}/${MAX_INITIALIZATION_ATTEMPTS})...`); // 1. Kumpulkan info tentang semua pertanyaan (sekali saat awal, lalu diperbarui jika perlu) collectAllQuestionsInfo(); // 2. Cek apakah sudah ada pertanyaan yang terdeteksi const firstVisiblePage = document.querySelector('.form-page[style*="block"]'); if (Object.keys(allQuestionsData).length > 0 && firstVisiblePage) { console.log("Initial questions detected and a visible form page found. Proceeding with UI creation."); clearInterval(initializationIntervalId); // Hentikan interval polling readCurrentAnswersFromForm(); // Baca state awal form updateControlPanel(); // Buat panel awal // 3. Set up MutationObserver untuk mendeteksi perubahan halaman const formContainer = document.querySelector('.form-container'); if (formContainer) { const config = { attributes: true, subtree: true, attributeFilter: ['style'] }; // Watch style changes on subtree observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'attributes' && mutation.attributeName === 'style' && mutation.target.classList.contains('form-page')) { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { console.log("Detected form-page style change. Updating control panel."); readCurrentAnswersFromForm(); updateControlPanel(); }, 200); } }); }); observer.observe(formContainer, config); console.log("MutationObserver set up for .form-container to watch for page changes."); } else { console.warn("Element .form-container not found. Page navigation watcher will not be active."); } } else { initializationAttempts++; if (initializationAttempts >= MAX_INITIALIZATION_ATTEMPTS) { clearInterval(initializationIntervalId); console.error("Failed to find survey questions or active form page after multiple attempts. Userscript UI will not be displayed."); alert("Userscript 'Auto-Fill Survey' tidak dapat memuat. Mungkin halaman survei belum sepenuhnya dimuat atau ada perubahan struktur HTML yang tidak sesuai."); } else { console.log("No questions or active form page found yet. Retrying..."); } } } // Jalankan inisialisasi secara berkala hingga elemen ditemukan atau batas upaya tercapai window.addEventListener('DOMContentLoaded', () => { initializationIntervalId = setInterval(initializeScript, 500); // Try every 500ms }); // Fallback and aggressive retry on 'load' event window.addEventListener('load', () => { if (initializationIntervalId !== null) { // If still polling from DOMContentLoaded clearInterval(initializationIntervalId); // Stop previous polling console.log("Load event fired. Attempting final initialization with aggressive retry."); initializationAttempts = 0; // Reset attempts to give it a full retry chance initializationIntervalId = setInterval(initializeScript, 100); // Faster retry after page load } else if (!document.getElementById('survey-auto-fill-per-question-panel')) { console.log("Load event fired and panel not found. Initiating one last robust attempt."); initializationAttempts = 0; initializeScript(); } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址