您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Trigger the page to load parts that are normally (since August 2025) lazily loaded, when that part of the page this shown. Works on Jira Cloud and Confluence Cloud as of August 2025.
// ==UserScript== // @name Trigger full page load for Jira Cloud and Confluence Cloud // @namespace https://greasyfork.org/users/1047370 // @description Trigger the page to load parts that are normally (since August 2025) lazily loaded, when that part of the page this shown. Works on Jira Cloud and Confluence Cloud as of August 2025. // @include https://*.atlassian.net/* // @include https://*.jira.com/* // @match https://*.atlassian.net/* // @match https://*.jira.com/* // @version 0.5 // @author Marnix Klooster <[email protected]> // @copyright public domain // @license public domain // @homepage https://greasyfork.org/scripts/546394 // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Debug flag - set to true for development logging const DEBUG = false; const processedElements = new WeakSet(); let isProcessing = false; const waitForPageLoad = () => { return new Promise(resolve => { if (document.readyState === 'complete') { resolve(); } else { window.addEventListener('load', resolve); } }); }; const createGlassPane = (scrollTop, scrollLeft) => { // Create the overlay container const pane = document.createElement('div'); pane.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: white; z-index: 999999; overflow: hidden; `; // Create viewport snapshot const snapshot = document.createElement('div'); snapshot.style.cssText = ` position: absolute; top: -${scrollTop}px; left: -${scrollLeft}px; width: ${document.documentElement.scrollWidth}px; height: ${document.documentElement.scrollHeight}px; pointer-events: none; transform-origin: 0 0; `; // Clone the current page content try { const bodyClone = document.body.cloneNode(true); // Remove any existing glass panes from the clone to avoid recursion const existingPanes = bodyClone.querySelectorAll('[style*="z-index: 999999"]'); existingPanes.forEach(pane => pane.remove()); // Remove scripts from clone to prevent execution const scripts = bodyClone.querySelectorAll('script'); scripts.forEach(script => script.remove()); snapshot.appendChild(bodyClone); } catch (e) { // Fallback to white background if cloning fails snapshot.style.background = 'white'; } pane.appendChild(snapshot); // Create progress overlay on top of screenshot const progressOverlay = document.createElement('div'); progressOverlay.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.9); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; `; const progressContainer = document.createElement('div'); progressContainer.style.cssText = ` background: white; border-radius: 8px; padding: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); min-width: 300px; text-align: center; `; const progressText = document.createElement('div'); progressText.textContent = 'Loading content...'; progressText.style.cssText = ` font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-size: 14px; color: #172B4D; margin-bottom: 16px; font-weight: 500; `; const progressTrack = document.createElement('div'); progressTrack.style.cssText = ` width: 100%; height: 4px; background: #DFE1E6; border-radius: 2px; overflow: hidden; `; const progressBar = document.createElement('div'); progressBar.style.cssText = ` width: 0%; height: 100%; background: #0052CC; border-radius: 2px; transition: width 0.3s ease; `; progressTrack.appendChild(progressBar); progressContainer.appendChild(progressText); progressContainer.appendChild(progressTrack); progressOverlay.appendChild(progressContainer); pane.appendChild(progressOverlay); document.body.appendChild(pane); const showProgress = () => { progressOverlay.style.opacity = '1'; }; return { pane, progressBar, showProgress }; }; const findEmptyDivs = () => { const emptyDivs = Array.from(document.querySelectorAll('div')) .filter(div => div.textContent.trim() === '' && div.attributes.length === 0 && !processedElements.has(div) ); // Log details about found empty divs (in natural DOM order) if (DEBUG && emptyDivs.length > 0) { console.log(`Found ${emptyDivs.length} empty divs:`); emptyDivs.forEach((div, index) => { const rect = div.getBoundingClientRect(); const top = rect.top + window.scrollY; const left = rect.left + window.scrollX; const parent = div.parentElement; const parentInfo = parent ? `${parent.tagName}${parent.className ? '.' + parent.className : ''}${parent.id ? '#' + parent.id : ''}` : 'no-parent'; console.log(` ${index + 1}. Position: (${Math.round(left)}, ${Math.round(top)}) Parent: ${parentInfo}`); }); } return emptyDivs; }; const waitForStability = (element, timeout = 50) => { return new Promise(resolve => { let timer; const observer = new MutationObserver(() => { clearTimeout(timer); timer = setTimeout(() => { observer.disconnect(); resolve(); }, timeout); }); observer.observe(element, { childList: true, subtree: true, attributes: true, characterData: true }); // Start initial timer in case no mutations occur timer = setTimeout(() => { observer.disconnect(); resolve(); }, timeout); }); }; const scrollToElement = async (element, index, total) => { // Get element's position relative to the document const rect = element.getBoundingClientRect(); const elementTop = rect.top + window.scrollY; const elementLeft = rect.left + window.scrollX; if (DEBUG) { console.log(`Processing ${index + 1}/${total}: Scrolling to (${Math.round(elementLeft)}, ${Math.round(elementTop)})`); } // Only scroll vertically to the element's y coordinate window.scrollTo({ top: elementTop, left: window.scrollX, // Keep current horizontal position behavior: 'smooth' }); await waitForStability(element); processedElements.add(element); // Log if element changed after processing if (DEBUG) { const newRect = element.getBoundingClientRect(); const hasContent = element.textContent.trim() !== ''; const hasAttributes = element.attributes.length > 0; if (hasContent || hasAttributes) { console.log(` ✓ Element loaded content: text=${hasContent}, attrs=${hasAttributes}`); } else { console.log(` ⚠ Element still empty after processing`); } } }; const processLazyElements = async () => { if (isProcessing) return; isProcessing = true; const emptyDivs = findEmptyDivs(); if (emptyDivs.length === 0) { isProcessing = false; return; } if (DEBUG) { console.log(`Processing ${emptyDivs.length} lazy-loaded elements`); } // Store original scroll position and page dimensions BEFORE any processing const originalScrollTop = window.scrollY; const originalScrollLeft = window.scrollX; const originalPageHeight = document.documentElement.scrollHeight; if (DEBUG) { console.log(`Starting position: scrollTop=${originalScrollTop}, pageHeight=${originalPageHeight}`); } // Create glass pane with the current (original) scroll position const { pane: glassPane, progressBar, showProgress } = createGlassPane(originalScrollTop, originalScrollLeft); // Wait 200ms before showing progress to prevent flashing for quick operations const showTimer = setTimeout(showProgress, 200); try { for (let i = 0; i < emptyDivs.length; i++) { const progress = ((i + 1) / emptyDivs.length) * 100; progressBar.style.width = `${progress}%`; await scrollToElement(emptyDivs[i], i, emptyDivs.length); } // Calculate safe scroll position after all processing is complete const newPageHeight = document.documentElement.scrollHeight; const maxScrollTop = Math.max(0, newPageHeight - window.innerHeight); const safeScrollTop = Math.min(originalScrollTop, maxScrollTop); if (DEBUG) { console.log(`Restoring scroll: original=${originalScrollTop}, safe=${safeScrollTop}, pageHeight=${originalPageHeight}->${newPageHeight}`); } // Restore scroll position BEFORE removing glass pane window.scrollTo({ top: safeScrollTop, left: originalScrollLeft, behavior: 'instant' }); // Wait for scroll to settle and any layout changes await new Promise(resolve => setTimeout(resolve, 100)); // Verify scroll position is correct before removing glass pane if (DEBUG) { const finalScrollTop = window.scrollY; console.log(`Final scroll position: ${finalScrollTop} (target was ${safeScrollTop})`); } } finally { clearTimeout(showTimer); glassPane.remove(); isProcessing = false; } }; const observePageChanges = () => { const observer = new MutationObserver((mutations) => { // Check if any new empty divs appeared if (DEBUG) { const newEmptyDivs = findEmptyDivs(); if (newEmptyDivs.length > 0) { console.log(`🔄 Page change detected, found ${newEmptyDivs.length} new empty divs after mutation`); // Log what type of mutations triggered this const mutationTypes = mutations.map(m => m.type).filter((v, i, a) => a.indexOf(v) === i); console.log(` Mutation types: ${mutationTypes.join(', ')}`); } } // Debounce to avoid excessive processing clearTimeout(observer.timer); observer.timer = setTimeout(processLazyElements, 100); }); observer.observe(document.body, { childList: true, subtree: true }); }; const init = async () => { await waitForPageLoad(); console.log('Page fully loaded, starting lazy element processing'); await processLazyElements(); observePageChanges(); }; init(); })();