PageSourceSearch

https://globalrecordings.net/js/grn-common.js

js globalrecordings.net collected 2026-09-24 09:53:17 UTC 8,618 bytes, 185 lines download raw bytes

1/*
2 * html5 speech recognition api for audio input
3 */
4function startDictation(searchform, searchbox, voiceicon, speakText, searchText) {
5	document.getElementById(searchbox).focus();
6	if (window.hasOwnProperty('webkitSpeechRecognition')) {
7		var recognition = new webkitSpeechRecognition();
8		recognition.continuous = false;
9		recognition.interimResults = false;
10		recognition.start();
11		document.getElementById(searchbox).placeholder = speakText;
12		document.getElementById(searchbox).classList.add('voiceactive');
13		document.getElementById(voiceicon).style['color'] = '#a01030';
14		recognition.onresult = function (e) {
15			document.getElementById(searchbox).value = e.results[0][0].transcript;
16			recognition.stop();
17			document.getElementById(searchform).submit();
18		};
19		recognition.onerror = function (e) {
20			recognition.stop();
21		};
22		recognition.onend = function (e) {
23			document.getElementById(searchbox).placeholder = searchText;
24			document.getElementById(searchbox).classList.remove('voiceactive');
25			document.getElementById(voiceicon).style['color'] = '#888';
26		};
27	}
28}
29
30/*
31 * Add events to button clicks tied to a corresponding dialog.
32 */
33function linkDialogAndButtons(dialog_id, button_open_id, button_close_id)
34{
35	const dialog = document.getElementById(dialog_id);
36	document.getElementById(button_open_id).addEventListener('click', () => { dialog.showModal() });
37	document.getElementById(button_close_id).addEventListener('click', () => { dialog.close() });
38}
39
40/*
41 * URL templates for the social media sharing dialogue, keyed to match the id suffix of each provider's
42 * anchor in the dialogue (eg 'baidu' -> #button-share-baidu). `url` and `title` are already
43 * encodeURIComponent()-escaped by the caller.
44 */
45const shareLinkBuilders = {
46	baidu: (url, title) => 'http://tieba.baidu.com/i/app/open_share_api?link=' + url + '&title=' + title,
47	blogger: (url, title) => 'https://www.blogger.com/blog-this.g?u=' + url + '&n=' + title,
48	// Reference: https://docs.bsky.app/docs/advanced-guides/intent-links
49	bluesky: (url, title) => 'https://bsky.app/intent/compose?text=' + title + '%20' + url,
50	// Reference: https://developers.facebook.com/docs/plugins/share-button/
51	facebook: (url, title) => 'https://www.facebook.com/sharer/sharer.php?u=' + url,
52	// Reference: https://developers.line.biz/en/docs/line-social-plugins/install-guide/using-line-share-buttons/
53	line: (url, title) => 'https://social-plugins.line.me/lineit/share?url=' + url + '&text=' + title,
54	// Reference: https://www.linkedin.com/pulse/how-make-custom-linkedin-share-button-your-website-ahmad-hayat-qm61f
55	linkedin: (url, title) => 'https://www.linkedin.com/shareArticle?url=' + url + '&title=' + title,
56	okru: (url, title) => 'https://connect.ok.ru/dk?st.shareUrl=' + url + '&st.title=' + title + '&st.cmd=WidgetSharePreview',
57	pinterest: (url, title) => 'https://www.pinterest.com/pin/create/button/?url=' + url + '&description=' + title,
58	qzone: (url, title) => 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=' + url + '&title=' + title,
59	reddit: (url, title) => 'https://www.reddit.com/submit?url=' + url + '&title=' + title,
60	// Reference: https://core.telegram.org/widgets/share
61	telegram: (url, title) => 'https://t.me/share?url=' + url + '&text=' + title,
62	threads: (url, title) => 'https://threads.net/intent/post?text=' + title + '%20' + url,
63	// Reference: https://help.tumblr.com/knowledge-base/share-button-documentation/
64	tumblr: (url, title) => 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + url + '&title=' + title,
65	// Reference: https://developers.viber.com/docs/tools/share-button/
66	viber: (url, title) => 'viber://forward?text=' + title + '%20' + url,
67	// Reference: https://dev.vk.com/en/widgets/share
68	vk: (url, title) => 'https://vk.com/share.php?url=' + url + '&title=' + title,
69	weibo: (url, title) => 'http://service.weibo.com/share/share.php?url=' + url + '&title=' + title,
70	whatsapp: (url, title) => 'https://wa.me/?text=' + title + '%20' + url,
71	x: (url, title) => 'https://twitter.com/intent/tweet?url=' + url + '&text=' + title,
72	// E-mail at end to separate from social media platforms.
73	email: (url, title) => 'mailto:?body=' + url + '&subject=' + title,
74};
75
76// Tracks the currently open share target so the analytics event below can report it.
77let currentShareUrl = '';
78
79/*
80 * Populate the single, page-wide share dialogue with a specific item's link and title, without
81 * opening it. Called both to seed the page-level default share target on page load, and by
82 * openShareDialog() below before it shows the dialogue - so any number of items on a page can share
83 * the one dialogue instead of each carrying its own copy.
84 */
85function populateShareDialog(url, title, qrcodeUrl) {
86	currentShareUrl = url;
87	const encodedUrl = encodeURIComponent(url);
88	const encodedTitle = encodeURIComponent(title);
89
90	for (const [id, buildLink] of Object.entries(shareLinkBuilders)) {
91		document.getElementById('button-share-' + id).href = buildLink(encodedUrl, encodedTitle);
92	}
93
94	document.getElementById('link-share-text').value = url;
95
96	const qrcodeImage = document.getElementById('link-share-qrcode');
97	qrcodeImage.src = qrcodeUrl;
98	qrcodeImage.alt = 'QR code for ' + url;
99}
100
101/*
102 * Populate the share dialogue for a specific item and open it. Used by share-trigger clicks.
103 */
104function openShareDialog(url, title, qrcodeUrl) {
105	populateShareDialog(url, title, qrcodeUrl);
106	document.getElementById('dialog-share').showModal();
107}
108
109/*
110 * Registered globally so picturebook-player can invoke it by name via an action's 'callback'
111 * field (see @glorecnet/gap-list's invokeActionCallback()), which calls it with a single params
112 * object rather than openShareDialog()'s positional arguments.
113 */
114function shareTrack(params) {
115	openShareDialog(params.url, params.title, params.qrCodeUrl);
116}
117
118/*
119 * Initialise the page-wide share dialogue: the close button, the copy-link button, per-provider click
120 * analytics, and delegated clicks from any number of share-trigger buttons/icons on the page (each
121 * carrying data-share-url/title/qrcode attributes, eg from grn_share_button() in common.php).
122 * Delegating on document means trigger elements can appear anywhere in the page, in any order
123 * relative to the dialogue itself.
124 */
125function initShareDialog() {
126	document.getElementById('button-share-close').addEventListener('click', () => {
127		document.getElementById('dialog-share').close();
128	});
129
130	document.getElementById('link-share-copy').addEventListener('click', () => {
131		navigator.clipboard.writeText(document.getElementById('link-share-text').value);
132
133		const copyNotification = document.getElementById('link-share-text-copied');
134		copyNotification.style.visibility = 'visible';
135		setTimeout(() => { copyNotification.style.visibility = 'hidden'; }, 2000);
136	});
137
138	// Google Tags for Analytics - gtag() is defined in html_scripttags() of htmltags.php, called from grn_startdoc().
139	// Reference: https://developers.google.com/analytics/devguides/collection/ga4/reference/events#share
140	const shareLinks = document.getElementById('dialog-share-container').getElementsByTagName('a');
141	for (const shareLink of shareLinks) {
142		shareLink.addEventListener('click', () => {
143			// Expected ID format: button-share-XXX
144			gtag('event', 'share', { 'method': shareLink.id.split('-')[2], 'content_type': document.documentElement.lang, 'item_id': currentShareUrl });
145		});
146	}
147
148	document.addEventListener('click', (event) => {
149		const trigger = event.target.closest('.button-share-trigger');
150		if (trigger) {
151			openShareDialog(trigger.dataset.shareUrl, trigger.dataset.shareTitle, trigger.dataset.shareQrcode);
152		}
153	});
154}
155
156/*
157 * Convert <time> fields to localised variants, including both relative and absolute dates.
158 */
159function localiseDates(language_tag) {
160	document.querySelectorAll('time[datetime]').forEach(timeElement => {
161		const elementDate = new Date(timeElement.dateTime);
162		const now = new Date();
163		const elapsedSeconds = (elementDate - now) / 1000;
164
165		const timeUnits = [
166			['year', 31536000],
167			['month', 2592000],
168			['week', 604800],
169			['day', 86400]
170		];
171
172		const relativeFormatter = new Intl.RelativeTimeFormat(language_tag, { numeric: 'auto' });
173		const absoluteFormatter = new Intl.DateTimeFormat(language_tag, { dateStyle: 'long' });
174
175		for (const [timeUnit, seconds] of timeUnits) {
176			const elapsedTime = elapsedSeconds / seconds;
177			if (Math.abs(elapsedTime) >= 1 || timeUnit === 'day') {
178				timeElement.textContent = relativeFormatter.format(Math.round(elapsedTime), timeUnit);
179				break;
180			}
181		}
182
183		timeElement.title = absoluteFormatter.format(elementDate);
184	});
185}

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.