PageSourceSearch

https://index.ieomsociety.org/assets/js/author.kickout.js?v=1790239350849

js ieomsociety.org collected 2026-09-24 08:42:49 UTC 6,489 bytes, 167 lines download raw bytes

1/**
2 * Author kickout — fixed-position hover panel (avatar + full name + org).
3 * Replaces native title tooltips on truncated author chips in card/list views.
4 */
5(function initAuthorKickout() {
6    var portal = null;
7    var hideTimer = null;
8    var activeTrigger = null;
9
10    function escapeHtml(str) {
11        return String(str || '')
12            .replace(/&/g, '&')
13            .replace(/</g, '&lt;')
14            .replace(/>/g, '&gt;')
15            .replace(/"/g, '&quot;');
16    }
17
18    function escapeAttr(str) {
19        return escapeHtml(str).replace(/'/g, '&#39;');
20    }
21
22    function truthy(val) {
23        return val === '1' || val === 'true' || val === true;
24    }
25
26    function ensurePortal() {
27        if (portal) return portal;
28        portal = document.createElement('div');
29        portal.id = 'cdp-author-kickout-portal';
30        portal.className = 'cdp-author-kickout';
31        portal.setAttribute('role', 'tooltip');
32        portal.innerHTML = '<div class="cdp-author-kickout__panel"></div>';
33        portal.addEventListener('mouseenter', function () {
34            clearTimeout(hideTimer);
35        });
36        portal.addEventListener('mouseleave', function (e) {
37            var related = e.relatedTarget;
38            if (related && activeTrigger && activeTrigger.contains(related)) return;
39            hideTimer = setTimeout(hideKickout, 120);
40        });
41        document.body.appendChild(portal);
42        return portal;
43    }
44
45    function buildAvatarHtml(pic, initials, color, showAvatar) {
46        if (!truthy(showAvatar)) return '';
47        if (pic) {
48            return '<img src="' + escapeAttr(pic) + '" class="cdp-author-kickout__avatar cdp-author-kickout__avatar--img" alt="">';
49        }
50        if (initials) {
51            return '<span class="cdp-author-kickout__avatar cdp-author-kickout__avatar--initials" style="background:' + escapeAttr(color || '#1e40af') + '">' + escapeHtml(initials) + '</span>';
52        }
53        return '';
54    }
55
56    function buildProfileLinkHtml(profileUrl) {
57        if (!profileUrl) return '';
58        return '<a href="' + escapeAttr(profileUrl) + '" class="cdp-author-kickout__profile-link" target="_blank" rel="noopener noreferrer">View profile</a>';
59    }
60
61    function buildRow(name, company, pic, initials, color, showAvatar, showCompany, profileUrl) {
62        var avatar = buildAvatarHtml(pic, initials, color, showAvatar);
63        var companyHtml = truthy(showCompany) && company
64            ? '<span class="cdp-author-kickout__company">' + escapeHtml(company) + '</span>'
65            : '';
66        var profileLinkHtml = buildProfileLinkHtml(profileUrl);
67        return '<div class="cdp-author-kickout__row">' +
68            (avatar ? '<div class="cdp-author-kickout__avatar-wrap">' + avatar + '</div>' : '') +
69            '<div class="cdp-author-kickout__text">' +
70            '<span class="cdp-author-kickout__name">' + escapeHtml(name) + '</span>' +
71            companyHtml +
72            profileLinkHtml +
73            '</div></div>';
74    }
75
76    function renderPanel(trigger) {
77        var panel = ensurePortal().querySelector('.cdp-author-kickout__panel');
78        var html = '';
79
80        if (trigger.getAttribute('data-kickout-multi') === '1') {
81            var raw = trigger.getAttribute('data-kickout-authors') || '[]';
82            try {
83                var authors = JSON.parse(raw);
84                if (Array.isArray(authors)) {
85                    authors.forEach(function (a) {
86                        html += buildRow(
87                            a.name || '',
88                            a.company || '',
89                            a.pic || '',
90                            a.initials || '',
91                            a.color || '#1e40af',
92                            a.showAvatar,
93                            a.showCompany,
94                            a.profileUrl || ''
95                        );
96                    });
97                }
98            } catch (e) { /* ignore malformed JSON */ }
99        } else {
100            html = buildRow(
101                trigger.getAttribute('data-kickout-name') || '',
102                trigger.getAttribute('data-kickout-company') || '',
103                trigger.getAttribute('data-kickout-pic') || '',
104                trigger.getAttribute('data-kickout-initials') || '',
105                trigger.getAttribute('data-kickout-color') || '#1e40af',
106                trigger.getAttribute('data-kickout-show-avatar'),
107                trigger.getAttribute('data-kickout-show-company'),
108                trigger.getAttribute('data-kickout-profile-url') || ''
109            );
110        }
111
112        panel.innerHTML = html;
113        return panel.innerHTML.length > 0;
114    }
115
116    function positionPortal(trigger) {
117        var el = ensurePortal();
118        var rect = trigger.getBoundingClientRect();
119        el.style.visibility = 'hidden';
120        el.classList.add('is-visible');
121        var pRect = el.getBoundingClientRect();
122        var gap = 8;
123        var left = rect.left + (rect.width / 2) - (pRect.width / 2);
124        var top = rect.top - pRect.height - gap;
125        if (top < gap) {
126            top = rect.bottom + gap;
127        }
128        left = Math.max(gap, Math.min(left, window.innerWidth - pRect.width - gap));
129        el.style.left = left + 'px';
130        el.style.top = top + 'px';
131        el.style.visibility = 'visible';
132    }
133
134    function showKickout(trigger) {
135        if (!renderPanel(trigger)) return;
136        positionPortal(trigger);
137        activeTrigger = trigger;
138    }
139
140    function hideKickout() {
141        if (portal) {
142            portal.classList.remove('is-visible');
143            portal.style.visibility = 'hidden';
144        }
145        activeTrigger = null;
146    }
147
148    document.addEventListener('mouseover', function (e) {
149        var trigger = e.target.closest('[data-author-kickout-trigger]');
150        if (!trigger) return;
151        clearTimeout(hideTimer);
152        if (activeTrigger !== trigger) {
153            showKickout(trigger);
154        }
155    });
156
157    document.addEventListener('mouseout', function (e) {
158        var trigger = e.target.closest('[data-author-kickout-trigger]');
159        if (!trigger || trigger !== activeTrigger) return;
160        var related = e.relatedTarget;
161        if (related && (trigger.contains(related) || (portal && portal.contains(related)))) return;
162        hideTimer = setTimeout(hideKickout, 120);
163    });
164
165    window.addEventListener('scroll', hideKickout, true);
166    window.addEventListener('resize', hideKickout);
167})();

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.