Bloomberg Footnote Popup

Shows Bloomberg article footnotes in a popup similar to Wikipedia

// ==UserScript==
// @name         Bloomberg Footnote Popup
// @version      1.1
// @description  Shows Bloomberg article footnotes in a popup similar to Wikipedia
// @author       https://greasyfork.org/en/users/1390485-fdsaasdf
// @match        https://www.bloomberg.com/*
// @grant        none
// @license      MIT
// @namespace https://greasyfork.org/users/1390485
// ==/UserScript==

(function() {
    'use strict';

    // Styles for the popup
    const styles = `
        .citation-popup {
            position: absolute;
            max-width: 300px;
            background: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            padding: 12px;
            font-size: 14px;
            line-height: 1.4;
            z-index: 1000;
            display: none;
        }
        .citation-popup a {
            color: #2962ff;
            text-decoration: underline;
        }
        .citation-popup a:hover {
            text-decoration: none;
        }
    `;

    // Add styles to the document
    const styleSheet = document.createElement('style');
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);

    // Create popup element
    const popup = document.createElement('div');
    popup.className = 'citation-popup';
    document.body.appendChild(popup);

    // Function to position popup near the citation link
    function positionPopup(link) {
        const rect = link.getBoundingClientRect();
        const scrollX = window.scrollX || window.pageXOffset;
        const scrollY = window.scrollY || window.pageYOffset;

        // Position popup below and slightly to the right of the citation
        popup.style.left = (rect.left + scrollX) + 'px';
        popup.style.top = (rect.bottom + scrollY + 5) + 'px';

        // Adjust position if popup would go off-screen
        const popupRect = popup.getBoundingClientRect();
        if (popupRect.right > window.innerWidth) {
            popup.style.left = (window.innerWidth - popupRect.width - 10) + 'px';
        }
    }

    // Function to get citation text from the footnotes section
    function getCitationText(citationId) {
        // Remove 'inline-' prefix to get the footer reference ID
        const footerRefId = citationId.replace('inline-', 'footer-');
        const footnote = document.getElementById(footerRefId);
        if (footnote) {
            // Clone the footnote to preserve its structure
            const footnoteClone = footnote.cloneNode(true);

            // Remove any "View in article" links
            const viewInArticleLinks = footnoteClone.querySelectorAll('a[href^="#inline-ref"]');
            viewInArticleLinks.forEach(link => link.remove());

            // Remove the counter div by matching the class pattern
            const counter = footnoteClone.querySelector('[class*="FootnoteItem_counter-"]');
            if (counter) {
                counter.remove();
            }

            // Get the div that contains all the paragraphs
            const contentDiv = footnoteClone.querySelector('div');
            if (contentDiv) {
                // Replace image placeholders with a more readable format
                const imgPlaceholders = contentDiv.querySelectorAll('p[data-component="paragraph"]');
                imgPlaceholders.forEach(p => {
                    if (p.textContent.includes('[imgviz')) {
                        p.textContent = '[Image]';
                    }
                });

                return contentDiv.innerHTML.trim();
            }

            return footnoteClone.innerHTML.trim();
        }
        return 'Citation not found';
    }

    // Function to show popup
    function showPopup(citationId, link) {
        const citationHtml = getCitationText(citationId);
        popup.innerHTML = citationHtml;
        popup.style.display = 'block';
        positionPopup(link);
    }

    // Function to hide popup with delay for smoother UX
    let hideTimeout;
    function hidePopup() {
        hideTimeout = setTimeout(() => {
            popup.style.display = 'none';
        }, 100); // Small delay to allow for mouse movement between link and popup
    }

    // Function to cancel hide if mouse moves to popup
    function cancelHide() {
        if (hideTimeout) {
            clearTimeout(hideTimeout);
        }
    }

    // Find all citation links
    function initializeCitationLinks() {
        // Updated selector to match Bloomberg's HTML structure
        const citationLinks = document.querySelectorAll('a[data-component="footnote-link"]');

        citationLinks.forEach(link => {
            // Show popup on hover
            link.addEventListener('mouseenter', (e) => {
                cancelHide();
                showPopup(link.id, link);
            });

            // Start hide timer when mouse leaves
            link.addEventListener('mouseleave', hidePopup);

            // Prevent default link behavior
            link.addEventListener('click', (e) => {
                e.preventDefault();
            });
        });
    }

    // Add event listeners to the popup itself
    popup.addEventListener('mouseenter', cancelHide);
    popup.addEventListener('mouseleave', hidePopup);

    // Initialize when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeCitationLinks);
    } else {
        initializeCitationLinks();
    }

    // Handle dynamic content loading (if Bloomberg uses AJAX)
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                initializeCitationLinks();
            }
        });
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();