PageSourceSearch

https://fizz.ca/sites/default/files/asset_injector/js/br_reviews_s…-e93c3db67cb1b629b15b8f095f820776.js?tlvemf

js fizz.ca collected 2026-09-24 21:11:47 UTC 8,900 bytes, 223 lines download raw bytes

1/**
2 * Homepage Reviews Slider
3 * Data source adapter for SliderUniversalV2.
4 * Uses reviewsData from clients-ranking-data.js and renders brand-refresh cards.
5 *
6 * Load order:
7 * 1. slider-universal.js
8 * 2. clients-ranking/clients-ranking-data.js
9 * 3. This file (reviews-slider.js)
10 */
11
12/* ── Star SVG helper ── */
13function homepageReviewsGenerateStars(rating) {
14    const starSvg = '<svg height="17" viewBox="0 0 16.75 16" xmlns="http://www.w3.org/2000/svg"><path d="M6.7,5.86c-.11.22-.32.38-.57.42l-3.73.55,2.7,2.63c.18.17.26.43.22.67l-.64,3.72,3.34-1.76c.22-.12.49-.12.71,0l3.34,1.76-.64-3.72c-.04-.25.04-.5.22-.67l2.7-2.63-3.73-.55c-.25-.04-.46-.19-.57-.42l-1.67-3.38-1.67,3.38ZM5.52,4.83L7.69.42c.28-.57,1.09-.57,1.36,0l2.17,4.41,4.86.71c.62.09.87.86.42,1.3l-3.52,3.43.83,4.84c.11.62-.55,1.1-1.1.8l-4.35-2.29-4.35,2.29c-.56.29-1.21-.18-1.1-.8l.83-4.84L.23,6.84c-.45-.44-.2-1.21.42-1.3l4.86-.71Z" fill="#00d672"/><path d="M6.7,5.86c-.11.22-.32.38-.57.42l-3.73.55,2.7,2.63c.18.17.26.43.22.67l-.64,3.72,3.34-1.76c.22-.12.49-.12.71,0l3.34,1.76-.64-3.72c-.04-.25.04-.5.22-.67l2.7-2.63-3.73-.55c-.25-.04-.46-.19-.57-.42l-1.67-3.38-1.67,3.38Z" fill="#00d672"/></svg>';
15    let stars = '';
16    for (let i = 0; i < Math.min(Math.round(rating), 5); i++) {
17        stars += starSvg;
18    }
19    return stars;
20}
21
22/* ── Date formatter ── */
23function homepageReviewsFormatDate(dateStr, lang) {
24    if (!dateStr) return '';
25    const parts = /^(\d{4})-(\d{2})-(\d{2})/.exec(dateStr);
26    if (!parts) return '';
27    const date = new Date(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3]));
28    const formatted = date.toLocaleDateString(lang === 'fr' ? 'fr-CA' : 'en-CA', {
29        day: '2-digit',
30        month: 'long',
31        year: 'numeric'
32    });
33    // Capitalize the first letter (e.g. "01 juin 2026" -> "01 Juin 2026")
34    return formatted.replace(/(^|[^a-zA-Z])([a-zA-Z])/, function (match, before, letter) {
35        return before + letter.toUpperCase();
36    });
37}
38
39/* ── Location formatter ── */
40function homepageReviewsFormatLocation(loc) {
41    if (!loc) return '';
42    return String(loc).trim().replace(/[a-zA-ZÀ-ÿ]+/g, function (word) {
43        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
44    });
45}
46
47/* ── Review filtering / sorting helpers ── */
48var REVIEW_REGION_MAP = {
49    QC: 'quebec',
50    ON: 'ontario',
51    MB: 'manitoba',
52    AB: 'alberta',
53    BC: 'britishcolumbia'
54};
55
56function normalizeRegionString(s) {
57    return (s || '').toLowerCase()
58        .normalize('NFD')
59        .replace(/[\u0300-\u036f]/g, '')
60        .replace(/[-\s]/g, '');
61}
62
63function getLocationTokens(loc) {
64    if (!loc) return [];
65    return String(loc).split(',').map(function (part) { return normalizeRegionString(part); });
66}
67
68function parseRegionFilter(regionAttr) {
69    if (!regionAttr || regionAttr === 'ANY') return null;
70    return regionAttr.split(/\s+/).map(function (term) {
71        var upper = term.toUpperCase();
72        return REVIEW_REGION_MAP[upper] || normalizeRegionString(term);
73    });
74}
75
76function reviewMatchesRegion(location, regionTerms) {
77    if (!regionTerms || regionTerms.length === 0) return true;
78    var tokens = getLocationTokens(location);
79    for (var i = 0; i < regionTerms.length; i++) {
80        for (var j = 0; j < tokens.length; j++) {
81            if (tokens[j] === regionTerms[i]) return true;
82        }
83    }
84    return false;
85}
86
87function sortReviewsForDisplay(reviews, order) {
88    order = (order || '').toLowerCase().trim();
89
90    if (order.indexOf('latest') === 0) {
91        return reviews.slice().sort(function (a, b) {
92            return (b.date || '').localeCompare(a.date || '');
93        });
94    }
95
96    if (order === 'random') {
97        var shuffled = reviews.slice();
98        for (var i = shuffled.length - 1; i > 0; i--) {
99            var j = Math.floor(Math.random() * (i + 1));
100            var tmp = shuffled[i];
101            shuffled[i] = shuffled[j];
102            shuffled[j] = tmp;
103        }
104        return shuffled;
105    }
106
107    return reviews.slice();
108}
109
110/* ── Data source registration ── */
111function registerHomepageReviewsDataSource() {
112    var containers = document.querySelectorAll('[data-slider="homepageReviews"]');
113    if (!containers.length) return;
114
115    containers.forEach(function (container, index) {
116        var maxReviews = parseInt(container.getAttribute('data-review-count'), 10) || 3;
117        if (maxReviews < 1) maxReviews = 3;
118
119        var order = (container.getAttribute('data-review-order') || '').toLowerCase().trim();
120        var regionAttr = (container.getAttribute('data-review-region') || '').toUpperCase().trim();
121        var regionTerms = parseRegionFilter(regionAttr);
122        var pageLang = (document.documentElement.lang || '').toLowerCase().split('-')[0];
123
124        var filteredReviews = reviewsData.filter(function (r) {
125            var content = pageLang === 'fr' ? (r.contentFR || r.content) : r.content;
126            return reviewMatchesRegion(r.location, regionTerms) && content && content.trim() !== '';
127        });
128
129        var cappedReviews = sortReviewsForDisplay(filteredReviews, order).slice(0, maxReviews);
130
131        var regionSuffix = (container.id || String(index)).replace(/^homepage-reviews-/, '');
132        var sourceName = 'homepageReviews-' + (regionSuffix || index);
133
134        window.SliderUniversalV2.registerDataSource(sourceName, cappedReviews, {
135
136            gridClass: 'slider-grid',
137            dotsClass: 'slider-nav-dots',
138            dotClass:  'slider-dot',
139            itemClass: 'slider-item',
140
141            breakpoints: { xl: 1200, lg: 992, md: 768, sm: 576, xs: 400 },
142            itemsPerBreakpoint: { xl: 3, lg: 3, md: 2, sm: 1, xs: 1, default: 1 },
143
144            peekAmount: 0.1,
145            gap: 16,
146
147            filterItems: function(item, lang) {
148                var content = lang === 'fr' ? (item.contentFR || item.content) : item.content;
149                return content && content.trim() !== '';
150            },
151
152            renderItem: function(item, lang) {
153                var title    = lang === 'fr' ? (item.titleFR   || item.title)   : item.title;
154                var content  = lang === 'fr' ? (item.contentFR || item.content) : item.content;
155                var date     = homepageReviewsFormatDate(item.date, lang);
156                var location = homepageReviewsFormatLocation(item.location);
157                var stars    = homepageReviewsGenerateStars(item.rating);
158
159                var el = document.createElement('div');
160                el.innerHTML =
161                    '<div class="card card-sm bg-surface-neutral-high radius-xl" style="height:100%;">' +
162                        '<div class="card-body" style="display:flex;flex-direction:column;">' +
163                            '<div class="d-flex gap-0-25 mb-3">' + stars + '</div>' +
164                            '<p><strong>' + title + '</strong></p>' +
165                            '<p style="flex:1;">' + content + '</p>' +
166                            '<p class="mb-0" style="margin-top:auto;"><strong>' + item.author + '</strong><br>' +
167                            '<small>' + (location ? location + ' | ' : '') + date + '</small></p>' +
168                        '</div>' +
169                    '</div>';
170                return el;
171            }
172        });
173
174        container.setAttribute('data-slider', sourceName);
175    });
176}
177
178function renderHomepageReviewsSlider() {
179    if (typeof window.SliderUniversalV2 === 'undefined') return;
180    var containers = document.querySelectorAll('[data-slider^="homepageReviews-"]');
181    containers.forEach(function (container) {
182        window.SliderUniversalV2.refresh(container);
183    });
184}
185
186/* ── Wait for dependencies, then register + render ──
187   Handles cases where clients-ranking-data.js and/or slider-universal.js
188   load after this script (e.g. Drupal library aggregation order). */
189function initHomepageReviewsWhenReady(retries) {
190    retries = retries || 0;
191    var maxRetries = 40; // ~4s at 100ms intervals
192
193    var sliderReady = typeof window.SliderUniversalV2 !== 'undefined';
194    var dataReady = typeof reviewsData !== 'undefined';
195
196    if (sliderReady && dataReady) {
197        registerHomepageReviewsDataSource();
198        renderHomepageReviewsSlider();
199        return;
200    }
201
202    if (retries >= maxRetries) {
203        if (!sliderReady) {
204            console.error('SliderUniversalV2 must be loaded before reviews-sli
204der.js');
205        }
206        if (!dataReady) {
207            console.error('reviewsData not found — load clients-ranking-data.js before reviews-slider.js');
208        }
209        return;
210    }
211
212    setTimeout(function () {
213        initHomepageReviewsWhenReady(retries + 1);
214    }, 100);
215}
216
217if (document.readyState === 'loading') {
218    document.addEventListener('DOMContentLoaded', function () {
219        initHomepageReviewsWhenReady();
220    });
221} else {
222    initHomepageReviewsWhenReady();
223}

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.