PageSourceSearch

https://bismikaallahuma.org/wp-content/plugins/citepress/assets/js/frontend.js?ver=3.0.1

js bismikaallahuma.org collected 2026-10-02 12:05:06 UTC 20,845 bytes, 470 lines download raw bytes

1/**
2 * Cite Plugin - Frontend JavaScript
3 * Version: 2.8.0
4 * 
5 * Handles citation display, copying, exporting, analytics tracking,
6 * and multiple display modes (dropdown, tabs, buttons).
7 */
8(function() {
9    'use strict';
10
11    if (typeof window.wpcpCitationInitialized !== 'undefined') {
12        return;
13    }
14    window.wpcpCitationInitialized = true;
15
16    function escapeHtml(text) {
17        var div = document.createElement('div');
18        div.textContent = text;
19        return div.innerHTML;
20    }
21
22    function showNotification(notification) {
23        if (!notification) return;
24        notification.classList.add('show');
25        setTimeout(function() {
26            notification.classList.remove('show');
27        }, 2000);
28    }
29
30    function hasAnalyticsConsent() {
31        var cookies = document.cookie;
32        if (cookies.indexOf('cookieyes-consent') !== -1) {
33            return cookies.indexOf('analytics:yes') !== -1;
34        }
35        if (cookies.indexOf('cmplz_statistics=allow') !== -1) {
36            return true;
37        }
38        if (cookies.indexOf('CookieConsent') !== -1) {
39            return cookies.indexOf('statistics:true') !== -1;
40        }
41        if (cookies.indexOf('moove_gdpr_popup') !== -1) {
42            try {
43                var match = cookies.match(/moove_gdpr_popup=([^;]+)/);
44                if (match) {
45                    var moove = JSON.parse(decodeURIComponent(match[1]));
46                    return moove && moove.thirdparty === '1';
47                }
48            } catch (e) { /* ignore */ }
49        }
50        // No recognised consent plugin detected — default to permitted.
51        // Only block when a known plugin is present and has explicitly denied analytics.
52        return true;
53    }
54
55    function trackAction(postId, action, style, ajaxUrl, enableAnalytics, requireConsent) {
56        if (!enableAnalytics) return;
57        if (postId <= 0) return;
58        if (typeof jQuery === 'undefined') return;
59
60        // Consent is evaluated server-side. The JS check was unreliable because
61        // it only recognised a subset of cookie formats and blocked valid requests.
62        // We pass the JS-detected hint as a best-effort signal; PHP makes the
63        // final decision via citepress_detect_consent_from_cookies() and the
64        // citepress_analytics_consent_granted filter.
65        var consentHint = !requireConsent || hasAnalyticsConsent() ? '1' : '0';
66
67        // FIX #3: nonce removed — nonces break under full-page caching.
68        // This endpoint writes only non-sensitive aggregated counters; no CSRF risk.
69        jQuery.post(ajaxUrl, {
70            action: 'citepress_track_analytics',
71            post_id: postId,
72            citation_style: style,
73            action_type: action,
74            consent_granted: consentHint
75        }).fail(function(xhr, status, error) {
76            console.error('Analytics tracking failed:', error);
77        });
78    }
79
80    /**
81     * Get the currently selected style from any display mode.
82     */
83    function getSelectedStyle(config) {
84        var mode = config.displayMode || 'dropdown';
85        var container = config.container;
86
87        if (mode === 'tabs') {
88            var activeTab = container.querySelector('.citepress-style-tab--active');
89            return activeTab ? activeTab.getAttribute('data-style') : config.defaultStyle;
90        } else if (mode === 'buttons') {
91            var activeBtn = container.querySelector('.citepress-style-btn--active');
92            return activeBtn ? activeBtn.getAttribute('data-style') : config.defaultStyle;
93        } else {
94            return config.dropdown ? config.dropdown.value : config.defaultStyle;
95        }
96    }
97
98    function updateCitation(config) {
99        var citationBox = config.citationBox;
100        var styles = config.styles;
101        if (!citationBox) return;
102
103        var selectedStyle = getSelectedStyle(config);
104        var template = styles[selectedStyle];
105        if (!template) return;
106
107        var citation = template
108            .replace('{author}',               escapeHtml(config.author))
109            .replace('{sitename}',             escapeHtml(config.siteName))
110            .replace('{title}',                escapeHtml(config.title))
111            .replace('{date}',                 escapeHtml(config.dateAccessed))
112            .replace('{date_dmy}',             escapeHtml(config.dateAccessedDMY || config.dateAccessed))
113            .replace('{publication_date}',     escapeHtml(config.publicationDate))
114            .replace('{publication_date_dmy}', escapeHtml(config.publicationDateDMY || config.publicationDate))
115            .replace('{publication_year}',     escapeHtml(config.publicationYear))
116            .replace('{permalink_url}',        escapeHtml(config.permalink))
117            .replace('{permalink}',            config.permalinkHTML)
118            .replace('{language}',             escapeHtml(config.language || 'en'));
119
120        if (selectedStyle === 'wikipedia') {
121            // Wikipedia output is plain markup — use textContent to avoid HTML interpretation
122            citationBox.textContent = citation;
123            citationBox.classList.add('wikipedia-format');
124        } else {
125            citation = citation.replace(/\s\./g, '.').replace(/\s\,/g, ',').replace(/\s+/g, ' ');
126            citationBox.innerHTML = citation;
127            citationBox.classList.remove('wikipedia-format');
128        }
129
130        trackAction(config.postId, 'view', selectedStyle, config.ajaxUrl, config.enableAnalytics, config.requireConsent);
131    }
132
133    function copyToClipboard(config) {
134        var citationBox = config.citationBox;
135        var notification = config.notification;
136        if (!citationBox) return;
137
138        var tempDiv = document.createElement('div');
139        tempDiv.innerHTML = citationBox.innerHTML;
140        var plainText = tempDiv.textContent || tempDiv.innerText || '';
141        var selectedStyle = getSelectedStyle(config);
142
143        if (navigator.clipboard && navigator.clipboard.writeText) {
144            navigator.clipboard.writeText(plainText).then(function() {
145                showNotification(notification);
146                trackAction(config.postId, 'copy', selectedStyle, config.ajaxUrl, config.enableAnalytics, config.requireConsent);
147            }).catch(function() {
148                fallbackCopy(plainText, config);
149            });
150        } else {
151            fallbackCopy(plainText, config);
152        }
153    }
154
155    function fallbackCopy(text, config) {
156        var textarea = document.createElement('textarea');
157        textarea.value = text;
158        textarea.style.position = 'fixed';
159        textarea.style.opacity = '0';
160        textarea.setAttribute('readonly', '');
161        document.body.appendChild(textarea);
162        textarea.select();
163        try {
164            document.execCommand('copy');
165            showNotification(config.notification);
166            var selectedStyle = getSelectedStyle(config);
167            trackAction(config.postId, 'copy', selectedStyle, config.ajaxUrl, config.enableAnalytics, config.requireConsent);
168        } catch (err) {
169            console.error('Failed to copy citation:', err);
170        }
171        document.body.removeChild(textarea);
172    }
173
174    function exportCitation(format, config) {
175        trackAction(config.postId, 'export', format, config.ajaxUrl, config.enableAnalytics, config.requireConsent);
176
177        var content = '';
178        var mimeType = 'text/plain;charset=utf-8';
179        var cleanTitle = config.title.replace(/"/g, '\\"').replace(/</g, '&lt;').replace(/>/g, '&gt;');
180        var cleanAuthor = config.author.replace(/"/g, '\\"').replace(/</g, '&lt;').replace(/>/g, '&gt;');
181        var cleanSiteName = config.siteName.replace(/"/g, '\\"').replace(/</g, '&lt;').replace(/>/g, '&gt;');
182        var citeKey = config.siteName.replace(/\s/g, '').replace(/[^a-zA-Z0-9]/g, '') + config.publicationYear;
183
184        if (format === 'bibtex') {
185            content = '@article{' + citeKey + ',\n  title={' + cleanTitle + '},\n  author={' + cleanAuthor + '},\n  journal={' + cleanSiteName + '},\n  year={' + config.publicationYear + '},\n  url={' + config.permalink + '}\n}';
186        } else if (format === 'ris') {
187            content = 'TY  - JOUR\nTI  - ' + cleanTitle + '\nAU  - ' + cleanAuthor + '\nJO  - ' + cleanSiteName + '\nPY  - ' + config.publicationYear + '\nUR  - ' + config.permalink + '\nER  -';
188        } else if (format === 'endnote') {
189            content = '%0 Journal Article\n%T ' + cleanTitle + '\n%A ' + cleanAuthor + '\n%J ' + cleanSiteName + '\n%D ' + config.publicationYear + '\n%U ' + config.permalink;
190        } else if (format === 'csl-json') {
191            var cslObj = {
192                'type': 'webpage',
193                'id': citeKey,
194                'title': config.title,
195                'author': [{ 'literal': config.author }],
196                'container-title': config.siteName,
197                'URL': config.permalink,
198                'issued': { 'date-parts': [[parseInt(config.publicationYear, 10)]] },
199                'accessed': { 'date-parts': [[new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]] },
200                'language': config.language || 'en'
201            };
202            content = JSON.stringify([cslObj], null, 2);
203            mimeType = 'application/json;charset=utf-8';
204        } else if (format === 'cff') {
205            var dateISO = config.publicationDateISO || config.publicationYear + '-01-01';
206            content = 'cff-version: 1.2.0\n';
207            content += 'message: "If you use this work, please cite it as below."\n';
208            content += 'title: "' + config.title.replace(/"/g, '\\"') + '"\n';
209            content += 'authors:\n';
210            content += '  - name: "' + config.author.replace(/"/g, '\\"') + '"\n';
211            content += 'date-released: ' + dateISO + '\n';
212            content += 'url: "' + config.permalink + '"\n';
213            content += 'type: article\n';
214            content += 'journal: "' + config.siteName.replace(/"/g, '\\"') + '"\n';
215        } else if (format === 'dublin-core') {
216            var dateISO = config.publicationDateISO || config.publicationYear + '-01-01';
217            content = '<?xml version="1.0" encoding="UTF-8"?>\n';
218            content += '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">\n';
219            content += '  <dc:title>' + config.title.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</dc:title>\n';
220            content += '  <dc:creator>' + config.author.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</dc:creator>\n';
221            content += '  <dc:publisher>' + config.siteName.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</dc:publisher>\n';
222            content += '  <dc:date>' + dateISO + '</dc:date>\n';
223            content += '  <dc:identifier>' + config.permalink.replace(/&/g, '&amp;') + '</dc:identifier>\n';
224            content += '  <dc:type>Text</dc:type>\n';
225            content += '  <dc:format>text/html</dc:format>\n';
226            content += '  <dc:language>' + (config.language || 'en') + '</dc:language>\n';
227            content += '</metadata>\n';
228            mimeType = 'application/xml;charset=utf-8';
229        }
230
231        var fileExtensions = {
232            'bibtex': 'bib',
233            'ris': 'ris',
234            'endnote': 'enw',
235            'csl-json': 'json',
236            'cff': 'cff',
237            'dublin-core': 'xml'
238        };
239
240        var blob = new Blob([content], { type: mimeType });
241        var url = window.URL.createObjectURL(blob);
242        var a = document.createElement('a');
243        a.href = url;
244        a.download = 'citation.' + (fileExtensions[format] || 'txt');
245        document.body.appendChild(a);
246        a.click();
247        document.body.removeChild(a);
248        window.URL.revokeObjectURL(url);
249    }
250
251    /**
252     * Set up tabs display mode event handlers.
253     */
254    function initTabsMode(config) {
255        var container = config.container;
256        var tabs = container.querySelectorAll('.citepress-style-tab');
257        if (!tabs.length) return;
258
259        tabs.forEach(function(tab) {
260            tab.addEventListener('click', function(e) {
261                e.preventDefault();
262                tabs.forEach(function(t) {
263                    t.classList.remove('citepress-style-tab--active');
264                    t.setAttribute('aria-selected', 'false');
265                    t.setAttribute('tabindex', '-1');
266                });
267                this.classList.add('citepress-style-tab--active');
268                this.setAttribute('aria-selected', 'true');
269                this.setAttribute('tabindex', '0');
270                updateCitation(config);
271            });
272
273            tab.addEventListener('keydown', function(e) {
274                var tabsArray = Array.prototype.slice.call(tabs);
275                var index = tabsArray.indexOf(this);
276                var newIndex = -1;
277
278                if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
279                    e.preventDefault();
280                    newIndex = (index + 1) % tabsArray.length;
281                } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
282                    e.preventDefault();
283                    newIndex = (index - 1 + tabsArray.length) % tabsArray.length;
284                } else if (e.key === 'Home') {
285                    e.preventDefault();
286                    newIndex = 0;
287                } else if (e.key === 'End') {
288                    e.preventDefault();
289                    newIndex = tabsArray.length - 1;
290                }
291
292                if (newIndex >= 0) {
293                    tabsArray[newIndex].click();
294                    tabsArray[newIndex].focus();
295                }
296            });
297        });
298    }
299
300    /**
301     * Set up buttons (pill) display mode event handlers.
302     */
303    function initButtonsMode(config) {
304        var container = config.container;
305        var buttons = container.querySelectorAll('.citepress-style-btn');
306        if (!buttons.length) return;
307
308        buttons.forEach(function(btn) {
309            btn.addEventListener('click', function(e) {
310                e.preventDefault();
311                buttons.forEach(function(b) {
312                    b.classList.remove('citepress-style-btn--active');
313                    b.setAttribute('aria-checked', 'false');
314                });
315                this.classList.add('citepress-style-btn--active');
316                this.setAttribute('aria-checked', 'true');
317                updateCitation(config);
318            });
319
320            btn.addEventListener('keydown', function(e) {
321                if (e.key === 'Enter' || e.key === ' ') {
322                    e.preventDefault();
323                    this.click();
324                }
325            });
326        });
327    }
328
329    function initCitationBox(data) {
330        var postIdStr = data.postId.toString();
331        var instanceStr = data.instance ? data.instance.toString() : '1';
332        var containerId = 'citation-box-' + postIdStr + '-' + instanceStr;
333        var container = document.getElementById(containerId);
334
335        if (!container) return;
336
337        var citationBox = container.querySelector('.citation-output');
338        var dropdown = container.querySelector('.citation-style-select');
339        var copyButton = container.querySelector('.copy-button');
340        var notification = container.querySelector('.copy-notification');
341        var exportButton = container.querySelector('.export-button');
342        var exportDropdown = exportButton ? exportButton.nextElementSibling : null;
343        var toggleButton = container.querySelector('.toggle-button');
344        var citationContent = container.querySelector('.citation-content');
345
346        var displayMode = data.displayMode || 'dropdown';
347        var defaultStyle = (data.activeFormats && data.activeFormats.length > 0) ? data.activeFormats[0] : 'apa';
348
349        // FIX #15: styles, ajaxUrl, enableAnalytics, requireConsent now live in
350        // citepressSharedData (output once per page) rather than duplicated per citation.
351        var shared = window.citepressSharedData || {};
352
353        var config = {
354            postId: data.postId,
355            container: container,
356            styles: shared.styles || {},
357            author: data.author,
358            authors: data.authors || [data.author],
359            siteName: data.siteName,
360            title: data.title,
361            dateAccessed: data.dateAccessed,
362            dateAccessedDMY: data.dateAccessedDMY || data.dateAccessed,
363            publicationDate: data.publicationDate,
364            publicationDateDMY: data.publicationDateDMY || data.publicationDate,
365            publicationYear: data.publicationYear,
366            publicationDateISO: data.publicationDateISO || data.publicationYear + '-01-01',
367            permalink: data.permalink,
368            permalinkHTML: '<a href="' + escapeHtml(data.permalinkUrl) + '" rel="bookmark">' + escapeHtml(data.permalink) + '</a>',
369            language: data.language || 'en',
370            ajaxUrl: shared.ajaxUrl || '',
371            enableAnalytics: shared.enableAnalytics || false,
372            requireConsent: shared.requireConsent || false,
373            dropdown: dropdown,
374            citationBox: citationBox,
375            notification: notification,
376            displayMode: displayMode,
377            defaultStyle: defaultStyle
378        };
379
380        // Bind display mode handlers
381        if (displayMode === 'tabs') {
382            initTabsMode(config);
383        } else if (displayMode === 'buttons') {
384            initButtonsMode(config);
385        } else if (dropdown) {
386            dropdown.addEventListener('change', function() {
387                updateCitation(config);
388            });
389        }
390
391        if (copyButton) {
392            copyButton.addEventListener('click', function(e) {
393                e.preventDefault();
394                copyToClipboard(config);
395            });
396            copyButton.addEventListener('keydown', function(e) {
397                if (e.key === 'Enter' || e.key === ' ') {
398                    e.preventDefault();
399                    copyToClipboard(config);
400                }
401            });
402        }
403
404        if (exportButton && exportDropdown) {
405            exportButton.addEventListener('click', function(e) {
406                e.preventDefault();
407                e.stopPropagation();
408                var isExpanded = exportDropdown.classList.toggle('show');
409                exportButton.setAttribute('aria-expanded', isExpanded.toString());
410            });
411
412            var exportOptions = exportDropdown.querySelectorAll('.export-option');
413            exportOptions.forEach(function(option) {
414                option.addEventListener('click', function(e) {
415                    e.preventDefault();
416                    var format = this.getAttribute('data-format');
417                    if (format) {
418                        exportCitation(format, config);
419                        exportDropdown.classList.remove('show');
420                        exportButton.setAttribute('aria-expanded', 'false');
421                    }
422                });
423                option.addEventListener('keydown', function(e) {
424                    if (e.key === 'Enter' || e.key === ' ') {
425                        e.preventDefault();
426                        var format = this.getAttribute('data-format');
427                        if (format) {
428                            exportCitation(format, config);
429                            exportDropdown.classList.remove('show');
430                            exportButton.setAttribute('aria-expanded', 'false');
431                        }
432                    }
433                });
434            });
435
436            document.addEventListener('click', function(e) {
437                if (!exportButton.contains(e.target) && !exportDropdown.contains(e.target)) {
438                    exportDropdown.classList.remove('show');
439                    exportButton.setAttribute('aria-expanded', 'false');
440                }
441            });
442        }
443
444        if (toggleButton && citationContent) {
445            toggleButton.addEventListener('click', function(e) {
446                e.preventDefault();
447                var isExpanded = !citationContent.classList.toggle('collapsed');
448                toggleButton.setAttribute('aria-expanded', isExpanded.toString());
449                toggleButton.classList.toggle('collapsed');
450            });
451        }
452
453        updateCitation(config);
454    }
455
456    function init() {
457        if (typeof window.citepressCitationData === 'undefined' || !Array.isArray(window.citepressCitationData)) {
458            return;
459        }
460        window.citepressCitationData.forEach(function(data) {
461            initCitationBox(data);
462        });
463    }
464
465    if (document.readyState === 'loading') {
466        document.addEventListener('DOMContentLoaded', init);
467    } else {
468        init();
469    }
470})();

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.