PageSourceSearch

https://www.jbjs.org/js/tooltip.js?v=8e1ba80c0c

js jbjs.org collected 2026-10-02 04:07:57 UTC 3,818 bytes, 124 lines download raw bytes

1/*
2	to show tooltip on hover add tooltip="xxx" attribute to an element
3	if element has 'text-with-ellipsis' class, the tooltip attr can be empty -
4	the whole text will be shown in this case if ellipsis is applied.
5
6	By default, tooltip is positioned above the element, aligned to its left
7	if there is no space for such a positioning, the tooltip may be shown underneath and right-aligned
8	This default can be changed to 'below by default'
9
10	On devices that support touch events, mouseenter event is fired on tap so we need
11	to check if it is a touch event or not.
12*/
13
14const touchEvent = {
15	isActive : false,
16	reset : function() {
17		this.isActive = false;
18	},
19	set: function() {
20		this.isActive = true;
21		setTimeout(this.reset, 30);
22	},
23	active : function() {
24		const active = this.isActive;
25		if (active) this.reset();
26		return active;
27	}
28}
29
30const tooltip = {
31	disabledNode : null,
32	remove : function() {
33		$('.tooltip').remove();
34	},
35	show : function(el, below) {
36		below = below || false; //true / false  = tooltip is below / above the element, above by default
37		this.remove();
38		if (this.disabledNode && this.disabledNode.contains(el)) {
39			return;
40		}
41		if (touchEvent.active()) {
42			return;
43		}
44		let text = el.getAttribute('tooltip');
45		if (text === null) {
46			return;
47		}
48		text = text.trim();
49		const $el = $(el);
50		const ellipsis = $el.hasClass('text-with-ellipsis');
51		if (!text && ellipsis) {
52			text = el.innerText.trim();
53		}
54		if (!text) {
55			return;
56		}
57		if (ellipsis && el.scrollHeight - el.offsetHeight <= 5) {
58			return;
59		}
60		const elBox = (el.hasAttribute('tooltip-parent') ? el.parentNode : el).getBoundingClientRect();
61		const elOffset = (el.hasAttribute('tooltip-parent') ? $el.parent() : $el).offset();
62		const oneLine = text.length < 40 || el.hasAttribute('tooltip-oneline');
63		const classes = el.className.split(' ').filter(x => x.startsWith("tooltip-")).join(" ");
64		const $tooltip = $('<div>', {
65			class : 'tooltip ' + classes,
66			html: text
67		}).css({
68			visibility: 'hidden',
69			left : elOffset.left,
70			'min-width' : oneLine ? 'auto' : Math.max(200, elBox.width),
71			'max-width' : oneLine ? 'auto' : Math.max(200, elBox.width * 1.2),
72			'white-space' : oneLine ? 'nowrap' : 'normal'
73		});
74
75		$(document.body).append($tooltip);
76		setTimeout(() => {
77			const layoutW = getLayoutAreaSize()[0];
78			const tooltipBox = $tooltip.get(0).getBoundingClientRect();
79			const alignBottom = below
80				? window.innerHeight - (elBox.bottom + tooltipBox.height) > 16   // below by default, above if not fit below
81				: elBox.top - tooltipBox.height < 16; // above by default, below if not fit above
82			const alignRight = layoutW - elBox.left - tooltipBox.width < 16;
83			$tooltip.css({
84				"top" : alignBottom ? (elOffset.top + elBox.height) : (elOffset.top - tooltipBox.height),
85				"left" : elOffset.left - (alignRight ? tooltipBox.width - elBox.width : (alignBottom ? -el.offsetWidth : 0)),
86				visibility: 'visible'
87			})
88		}, 0)
89	},
90	enable : function () {
91		this.disabledNode = null;
92	},
93	disable : function (element) {
94		// disable tooltip for descendants of the element
95		if (element) {
96			this.disabledNode = element;
97			this.remove();
98		}
99	}
100}
101
102window.tooltip = {
103	remove : tooltip.remove,
104	enable : tooltip.enable,
105	disable : tooltip.disable
106};
107
108const tooltipBelowSelectors = [
109	'.article-info', // bottom tooltip for article block
110	'#bookmarks-container[mode=list]', // bookmarks
111	'.podcast-info', // podcasts search
112	'.product-info'  // product pages search
113];
114
115$(document).ready(function() {
116	$(document).on({
117		mouseenter: function() {
118			const below = tooltipBelowSelectors.some(selector => this.closest(selector) != null);
119			tooltip.show(this, below);
120		},
121		mouseleave: function() { tooltip.remove(); },
122		touchend:   function() { touchEvent.set(); }
123	}, '.desktop [tooltip]');
124});

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.