PageSourceSearch

https://www.ispazio.net/wp-content/themes/ispazio-theme/assets/js/load-more.js?x58667&ver=1790235968

js ispazio.net collected 2026-09-24 17:12:45 UTC 9,156 bytes, 187 lines download raw bytes

1/**
2 * iSpazio — Shared "Carica altri" loader for home sections.
3 *
4 * Hooks any element with [data-isp-section] that contains:
5 *   - a target wrapper [data-load-more-target] (where new cards are appended)
6 *   - a button [data-isp-load-more]
7 *   - a list of currently-rendered IDs in [data-current-ids="1,2,3,..."]
8 *
9 * Fetches /wp-json/ispazio/v1/section/{key}/load-more with the exclude list,
10 * receives { html, post_ids, has_more }, appends html and updates state.
11 *
12 * Note: html comes from a same-origin endpoint that renders cards through theme
13 * partials with esc_html/esc_attr — same trust model as the existing article-grid.
14 */
15(function () {
16    'use strict';
17
18    var REST_BASE = (window.ISP_LOAD_MORE && window.ISP_LOAD_MORE.rest)
19        || '/wp-json/ispazio/v1';
20
21    // Carica featured-video.js al volo se contenuto appena inserito contiene un
22    // featured video e il controller non è ancora presente. Idempotente.
23    function ensureFeaturedVideo(scope) {
24        if (window.__ispFvLoaded) return;                  // già caricato/presente
25        if (!scope || !scope.querySelector || !scope.querySelector('[data-isp-fvideo]')) return;
26        var src = window.ISP_LOAD_MORE && window.ISP_LOAD_MORE.fvideoSrc;
27        if (!src) return;
28        window.__ispFvLoaded = true;                       // evita iniezioni multiple
29        var s = document.createElement('script');
30        s.src = src; s.defer = true;
31        document.head.appendChild(s);
32    }
33
34    function init(section) {
35        if (section._ispLm) return;
36        section._ispLm = true;
37
38        var key   = section.getAttribute('data-isp-section');
39        var tax   = section.getAttribute('data-isp-section-tax');
40        var slug  = section.getAttribute('data-isp-section-slug');
41        var batch = parseInt(section.getAttribute('data-isp-section-batch') || '0', 10);
42        // Filtri liberi (categorie, tag, ID) → /custom/load-more. Il valore è una
43        // query string già pronta, generata da ispazio_load_more_attrs().
44        var custom = section.getAttribute('data-isp-lm-custom');
45
46        var target = section.querySelector('[data-load-more-target]');
47        if (!target && section.hasAttribute('data-load-more-target')) target = section;
48        var btn = section.querySelector('[data-isp-load-more]');
49        if (!target || !btn) return;
50        if (!key && !(tax && slug) && custom === null) return;
51
52        var endpoint = key
53            ? '/section/' + encodeURIComponent(key) + '/load-more'
54            : (tax && slug)
55                ? '/' + encodeURIComponent(tax) + '/' + encodeURIComponent(slug) + '/load-more'
56                : '/custom/load-more';
57
58        var labelLabel = btn.querySelector('[data-isp-load-more-label]') || btn;
59        var labelDefault = labelLabel.dataset.label || labelLabel.textContent.trim();
60        var labelLoading = labelLabel.dataset.loadingLabel || 'Caricamento…';
61
62        function getCurrentIds() {
63            var raw = section.getAttribute('data-current-ids') || '';
64            return raw.split(',').map(function (s) { return parseInt(s, 10); }).filter(function (n) { return n > 0; });
65        }
66        function setCurrentIds(ids) {
67            section.setAttribute('data-current-ids', ids.join(','));
68        }
69
70        /*
71         * La regione che annuncia l'esito.
72         *
73         * Chi naviga da tastiera o con uno screen reader premeva «carica altro»
74         * e non riceveva niente: arrivavano sei articoli in fondo alla pagina,
75         * fuori dal punto in cui si trovava, e nessuno glielo diceva. Una riga
76         * annunciata costa un nodo vuoto e chiude il buco.
77         *
78         * `polite`: si aspetta una pausa nella lettura invece di interrompere.
79         */
80        var live = document.createElement('span');
81        live.className = 'sr-only';
82        live.setAttribute('role', 'status');
83        live.setAttribute('aria-live', 'polite');
84        section.appendChild(live);
85
86        /*
87         * Dove va il focus dopo il caricamento.
88         *
89         * Non sul primo articolo nuovo — sarebbe un salto, e la pagina si
90         * muoverebbe sotto gli occhi di chi ci vede benissimo. Sul contenitore
91         * dei nuovi, reso raggiungibile solo da codice (`tabindex="-1"`), cosi'
92         * il prossimo Tab riparte da li' invece che dall'inizio del documento.
93         */
94        function focusFirstNew(firstNew) {
95            if (!firstNew) return;
96            if (!firstNew.hasAttribute('tabindex')) firstNew.setAttribute('tabindex', '-1');
97            try { firstNew.focus({ preventScroll: true }); } catch (e) { firstNew.focus(); }
98        }
99
100        var loading = false;
101        btn.addEventListener('click', function () {
102            if (loading) return;
103            loading = true;
104            btn.disabled = true;
105            labelLabel.dataset.label = labelDefault;
106            labelLabel.textContent = labelLoading;
107
108            var ids = getCurrentIds();
109            var url = REST_BASE + endpoint
110                + '?exclude=' + encodeURIComponent(ids.join(','));
111            if (batch > 0) url += '&count=' + batch;
112            if (custom && !key && !(tax && slug)) url += '&' + custom;
113            var partial = section.getAttribute('data-isp-lm-partial');
114            if (partial) url += '&partial=' + encodeURIComponent(partial);
115
116            fetch(url, { credentials: 'same-origin' })
117                .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
118                .then(function (data) {
119                    if (data && data.html) {
120                        var tmp = document.createElement('div');
121                        // Same-origin trusted output rendered by theme partials.
122                        tmp.innerHTML = data.html;
123                        var frag = document.createDocumentFragment();
124                        var added = [];
125                        while (tmp.firstChild) {
126                            if (tmp.firstChild.nodeType === 1) added.push(tmp.firstChild);
127                            frag.appendChild(tmp.firstChild);
128                        }
129                        target.appendChild(frag);
130
131                        // Prima il focus, poi l'annuncio: chi legge con lo
132                        // screen reader sente «sei articoli aggiunti» e si trova
133                        // gia' nel punto giusto per continuare col Tab.
134                        focusFirstNew(added[0]);
135                        live.textContent = added.length === 1
136                            ? '1 articolo aggiunto'
137                            : added.length + ' articoli aggiunti';
138
139                        // Se le nuove card contengono un featured video ma il
140                        // controller non è ancora caricato (la prima infornata non
141                        // aveva video → enqueue condizionale l'aveva saltato), lo
142                        // carichiamo al volo: al boot farà scan(document) e prenderà
143                        // anche queste card.
144                        ensureFeaturedVideo(target);
145
146                        // Primitiva generica: notifica che è stato aggiunto contenuto
147                        // dinamico. featured-video.js (ed eventuali futuri lazy-feature)
148                        // ascoltano e ri-scansionano solo i nuovi nodi (idempotente).
149                        document.dispatchEvent(new CustomEvent('ispazio:content-added', {
150                            detail: { container: target }
151                        }));
152
153                        if (Array.isArray(data.post_ids) && data.post_ids.length) {
154                            setCurrentIds(ids.concat(data.post_ids.map(function (n) { return parseInt(n, 10); })));
155                        }
156                        if (window._ispSyncBookmarks) window._ispSyncBookmarks();
157                    }
158                    if (!data || data.has_more === false) {
159                        btn.style.display = 'none';
160                        // Il pulsante sparisce: senza una parola, chi non lo
161                        // vede sparire non sa che l'elenco e' finito.
162                        live.textContent = (live.textContent ? live.textContent + '. ' : '') + 'N
162on ci sono altri articoli.';
163                    }
164                })
165                .catch(function () {
166                    // Il pulsante resta visibile per riprovare, ma il perche'
167                    // va detto: prima, un clic che falliva non produceva nulla.
168                    live.textContent = 'Non è stato possibile caricare altri articoli. Riprova.';
169                })
170                .then(function () {
171                    loading = false;
172                    btn.disabled = false;
173                    labelLabel.textContent = labelDefault;
174                });
175        });
176    }
177
178    function bootstrap() {
179        document.querySelectorAll('[data-isp-section][data-current-ids],[data-isp-section-tax][data-current-ids],[data-isp-lm-custom][data-current-ids]').forEach(init);
180    }
181
182    if (document.readyState === 'loading') {
183        document.addEventListener('DOMContentLoaded', bootstrap);
184    } else {
185        bootstrap();
186    }
187})();

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.