Bloomberg Footnote Popup

Shows Bloomberg article footnotes in a popup similar to Wikipedia

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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
    });
})();