Greasy Fork

Greasy Fork is available in English.

Trigger full page load for Jira Cloud and Confluence Cloud

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.

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Trigger full page load for Jira Cloud and Confluence Cloud
// @namespace   http://greasyfork.icu/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.4
// @author      Marnix Klooster <[email protected]>
// @copyright   public domain
// @license     public domain
// @homepage    http://greasyfork.icu/scripts/546394
// @grant       none
// @run-at      document-start
// ==/UserScript==

(function() {
    'use strict';
    
    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 = () => {
        return Array.from(document.querySelectorAll('div'))
            .filter(div => 
                div.textContent.trim() === '' && 
                div.attributes.length === 0 &&
                !processedElements.has(div)
            );
    };
    
    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) => {
        element.scrollIntoView({
            behavior: 'smooth',
            block: 'center',
            inline: 'center'
        });
        
        await waitForStability(element);
        processedElements.add(element);
    };
    
    const processLazyElements = async () => {
        if (isProcessing) return;
        isProcessing = true;
        
        const emptyDivs = findEmptyDivs();
        
        if (emptyDivs.length === 0) {
            isProcessing = false;
            return;
        }
        
        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;
        
        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]);
            }
            
            // 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);
            
            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
            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(() => {
            // 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();
    
})();