PageSourceSearch

https://pjmedia.com/_content/Rcl.Common/js/trending-tooltip.js

js pjmedia.com collected 2026-10-02 02:19:30 UTC 3,429 bytes, 97 lines download raw bytes

1/**
2 * TrendingTooltip - Bootstrap popovers for trending items
3 * @module trending-tooltip
4 * @version 1.0.0
5 */
6
7class TrendingTooltip {
8    static popovers = new Map();
9
10    static init() {
11        // Skip hover cards on touch/coarse-pointer devices - hover popovers hijack
12        // the first tap (showing the card instead of following the link) and can
13        // obscure the tapped item on small screens.
14        if (!this.supportsHover()) {
15            console.log('[TrendingTooltip] Hover not supported, skipping popovers');
16            return;
17        }
18
19        const items = document.querySelectorAll('[data-trending-tooltip]');
20        items.forEach(item => {
21            const existingPopover = bootstrap.Popover.getInstance(item);
22            if (existingPopover) {
23                existingPopover.dispose();
24                this.popovers.delete(item);
25            }
26
27            const site = item.getAttribute('data-trending-site') || '';
28            const description = item.getAttribute('data-trending-description') || '';
29            const minutes = parseInt(item.getAttribute('data-trending-minutes') || '0');
30            const origin = item.getAttribute('data-trending-origin') || '';
31            const timeText = this.formatMinutesAgo(minutes);
32
33            const content = `
34                <div class="d-flex flex-column gap-2">
35                    <div>
36                        <div class="fw-bold fs-6 mb-1">${this.escapeHtml(site)}</div>
37                        <div class="text-muted small">${timeText}</div>
38                    </div>
39                    <div class="small lh-base">${this.escapeHtml(description)}</div>
40                    <div class="text-muted" style="font-size: 0.75rem;">${this.escapeHtml(origin)}</div>
41                </div>
42            `;
43
44            const popover = new bootstrap.Popover(item, {
45                trigger: 'hover',
46                placement: 'top',
47                html: true,
48                content: content,
49                container: 'body',
50                customClass: 'trending-popover',
51                delay: { show: 300, hide: 100 }
52            });
53
54            this.popovers.set(item, popover);
55        });
56
57        console.log('[TrendingTooltip] Initialized', this.popovers.size, 'tooltips');
58    }
59
60    static supportsHover() {
61        return window.matchMedia('(hover: hover) and (pointer: fine)').matches;
62    }
63
64    static formatMinutesAgo(minutes) {
65        if (minutes < 1) return 'Just now';
66        if (minutes === 1) return '1 minute ago';
67        if (minutes < 60) return `${minutes} minutes ago`;
68        if (minutes < 120) return '1 hour ago';
69        if (minutes < 1440) {
70            const hours = Math.floor(minutes / 60);
71            return `${hours} hours ago`;
72        }
73        const days = Math.floor(minutes / 1440);
74        return days === 1 ? '1 day ago' : `${days} days ago`;
75    }
76
77    static escapeHtml(text) {
78        const div = document.createElement('div');
79        div.textContent = text;
80        return div.innerHTML;
81    }
82
83    static destroy() {
84        this.popovers.forEach((popover) => popover.dispose());
85        this.popovers.clear();
86        console.log('[TrendingTooltip] Destroyed');
87    }
88}
89
90// Auto-initialize (modules are deferred, DOM is ready)
91TrendingTooltip.init();
92
93// Export for other modules
94export { TrendingTooltip };
95
96// Expose globally for debugging
97window.TrendingTooltip = TrendingTooltip;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.