diff --git a/.gitignore b/.gitignore index 89c9465..a4bc10d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ _ignored -.venv \ No newline at end of file +.venv +.playwright-mcp +.vscode \ No newline at end of file diff --git a/tampermonkey-script/GAIA-Swissknife.user.js b/tampermonkey-script/GAIA-Swissknife.user.js index e09149a..62c9606 100644 --- a/tampermonkey-script/GAIA-Swissknife.user.js +++ b/tampermonkey-script/GAIA-Swissknife.user.js @@ -5,7 +5,7 @@ // @downloadURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/GAIA-Swissknife.user.js // @changelogURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/CHANGELOG.md // @author Luigi D'Acunto -// @version 1.1.006 +// @version 1.1.009 // @description Aggiunge funzionalità alle pagine di GAIA // @match https://gaia.cri.it/* // @require https://code.jquery.com/jquery-3.6.0.min.js @@ -13,6 +13,8 @@ // @resource DataTablesCSS https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css // @grant GM_addStyle // @grant GM_getResourceText +// @grant GM_getValue +// @grant GM_setValue // @run-at document-idle // ==/UserScript== @@ -29,18 +31,21 @@ GM_addStyle(GM_getResourceText("DataTablesCSS")); // Inizializza le funzionalità - // Funzione per aggiungere il blocco di intestazione + // Funzione per aggiungere il blocco di intestazione in cima alla sidebar function ensureSwissknifeHeader() { - console.log("GAIA Swissknife: Aggiungo intestazione GAIA Swissknife..."); - if ($('#sezione').length > 0 && $('#gaia-swissknife-header').length === 0) { - const headerHTML = ` -
- - `; - $('#sezione').append(headerHTML); - } + if ($('#sezione').length === 0 || $('#gaia-swissknife-header').length > 0) return; + console.log("GAIA Swissknife: Aggiungo intestazione GAIA Swissknife in cima..."); + const $header = $(``); + const $divider = $('
'); + // Prepend: header prima, poi divider — risultato: [header][divider][voci esistenti] + // I pulsanti vengono inseriti tra header e divider + $('#sezione').prepend($divider).prepend($header); + } + + // Helper: inserisce un
  • con il pulsante tra l'header Swissknife e il divider + function appendToSwissknifeSection($li) { + ensureSwissknifeHeader(); + $('#gaia-swissknife-divider').before($li); } // Funzione per correggere i filtri della sidebar delle autorizzazioni @@ -231,26 +236,18 @@ GM_addStyle(GM_getResourceText("DataTablesCSS")); if ($('#sezione').length > 0) { console.log("GAIA Swissknife: Elementi trovati, aggiungo i pulsanti nel menu laterale..."); - ensureSwissknifeHeader(); // Aggiungi i pulsanti per settare gli esiti - const buttonsHTML = ` -
  • - -
  • -
  • - -
  • -
  • - -
  • - `; - $('#sezione').append(buttonsHTML); + [ + { id: 'button_set_ampp', label: 'Ammessi, Positivo, Positivo', color: '#007BFF' }, + { id: 'button_set_ampo', label: 'Ammessi, Positivo, Non previsto', color: '#28a745' }, + { id: 'button_set_amop', label: 'Ammessi, Non previsto, Positivo', color: 'rgb(167, 40, 99)' } + ].forEach(({ id, label, color }) => { + appendToSwissknifeSection( + $('
  • ').append( + $(``) + ) + ); + }); // Evento per il pulsante "Tutti Ammessi/Esito Positivo x2" $('#button_set_ampp').click(() => { @@ -344,9 +341,7 @@ GM_addStyle(GM_getResourceText("DataTablesCSS")); // Inserimento del pulsante nel menu laterale o in floating if ($('#sezione').length > 0) { - ensureSwissknifeHeader(); - - $('#sezione').append( + appendToSwissknifeSection( $('
  • ').append(openButton) ); } else { @@ -585,6 +580,240 @@ GM_addStyle(GM_getResourceText("DataTablesCSS")); applyColoring(); } + // Funzione per la selezione rapida del comitato preferito (persistente cross-sessione) + function addSediPreferitaTool() { + if ($('table tbody input[name="sedi"]').length === 0) return; + + console.log("GAIA Swissknife: Aggiungo tool selezione sede preferita..."); + + // Costruisce la mappa di tutte le sedi dalla tabella + function buildSediMap() { + const map = []; + document.querySelectorAll('table tbody tr').forEach(row => { + const cb = row.querySelector('input[name="sedi"]'); + const nameTd = row.querySelector('td.grassetto'); + if (!cb || !nameTd) return; + map.push({ + value: cb.value, + label: nameTd.innerText.trim(), + numCells: row.querySelectorAll('td').length + }); + }); + return map; + } + + const sediMap = buildSediMap(); + const savedValue = GM_getValue('preferred_sede_value', ''); + + // Select con indentazione visiva per la gerarchia + const $select = $('').css({ + width: '100%', padding: '4px', margin: '4px 5px 4px 5px', + fontSize: '12px', boxSizing: 'border-box', width: 'calc(100% - 10px)' + }); + $select.append(''); + sediMap.forEach(({ value, label, numCells }) => { + const prefix = numCells === 4 ? '' : numCells === 5 ? '\u00a0\u00a0\u00a0' : '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'; + const $opt = $(``); + if (value === savedValue) $opt.prop('selected', true); + $select.append($opt); + }); + + $select.on('change', function () { + GM_setValue('preferred_sede_value', $(this).val()); + }); + + // Pulsante: seleziona solo il preferito (+ figli se è Regionale o Locale) + const $btnSolo = $(''); + $btnSolo.on('click', function () { + const val = $select.val(); + if (!val) { alert('Seleziona prima un comitato preferito.'); return; } + + $('input[name="sedi"]').prop('checked', false); + + const idx = sediMap.findIndex(s => s.value === val); + if (idx === -1) return; + + const selected = sediMap[idx]; + $(`#c_${selected.value}`).prop('checked', true); + + // Se è Regionale (4) o Locale (5), seleziona anche tutti i figli + if (selected.numCells <= 5) { + for (let i = idx + 1; i < sediMap.length; i++) { + if (sediMap[i].numCells <= selected.numCells) break; + $(`#c_${sediMap[i].value}`).prop('checked', true); + } + } + + console.log(`GAIA Swissknife: Selezionato solo "${selected.label}" e figli.`); + }); + + // Pulsante: seleziona tutto + const $btnAll = $(''); + $btnAll.on('click', function () { + $('input[name="sedi"]').prop('checked', true); + console.log('GAIA Swissknife: Tutte le sedi selezionate.'); + }); + + appendToSwissknifeSection( + $('
  • ').append($select, $btnSolo, $btnAll) + ); + } + + // Funzione per copiare il catalogo corsi in clipboard come JSON + function addCopyCatalogButton() { + if (!window.location.href.includes('/courses/catalog')) return; + if (!$('.corso-nome').length) return; + + console.log("GAIA Swissknife: Aggiungo pulsante copia catalogo corsi..."); + + function extractCatalogo() { + const main = document.querySelector('.col-md-9'); + if (!main) return []; + const results = []; + let currentTipo = ''; + for (const el of [...main.children]) { + if (el.classList.contains('area-nome')) { + currentTipo = el.textContent.trim(); + continue; + } + if (el.classList.contains('area-levels') && currentTipo) { + let currentObiettivo = ''; + for (const child of [...el.children]) { + if (child.classList.contains('area-nome')) { + currentObiettivo = child.textContent.trim(); + } else if (child.classList.contains('area-levels') && currentObiettivo) { + let currentLivello = ''; + for (const inner of [...child.children]) { + if (inner.tagName === 'H4') { + currentLivello = inner.textContent.trim(); + } else if (inner.tagName === 'SECTION') { + const corsoNome = inner.querySelector('.corso-nome'); + if (!corsoNome) continue; + const codice = corsoNome.querySelector('strong')?.textContent.trim() || ''; + const nome = corsoNome.textContent.trim().replace(/\s*\([^)]*\)\s*$/, '').trim(); + const scheda_pdf = inner.querySelector('.corso-scheda-completa a')?.href || null; + results.push({ nome, codice, livello: currentLivello, tipo: currentTipo, obiettivo: currentObiettivo, scheda_pdf }); + } + } + } + } + } + } + return results; + } + + const $btn = $(''); + + $btn.on('click', function () { + const catalogo = extractCatalogo(); + if (!catalogo.length) { alert('Nessun corso trovato.'); return; } + + navigator.clipboard.writeText(JSON.stringify(catalogo, null, 2)).then(() => { + console.log(`GAIA Swissknife: Catalogo copiato — ${catalogo.length} corsi.`); + const orig = $(this).text(); + $(this).text(`✓ Copiato! (${catalogo.length} corsi)`).css('background-color', '#28a745'); + setTimeout(() => $(this).text(orig).css('background-color', '#c0504d'), 3000); + }).catch(err => { + console.error('GAIA Swissknife: Errore clipboard:', err); + alert('Errore durante la copia in clipboard.'); + }); + }); + + if ($('#sezione').length > 0) { + appendToSwissknifeSection($('
  • ').append($btn)); + } + } + + // Funzione per estrarre la gerarchia sedi e copiarla in clipboard come JSON + function addCopyStructureButton() { + if (!window.location.href.includes('/us/elenchi/volontari/')) return; + + // Il pulsante ha senso solo quando la tabella sedi è visibile (non nella vista risultati con iframe) + if ($('table tbody tr').length === 0) return; + + console.log("GAIA Swissknife: Aggiungo pulsante copia struttura sedi..."); + + function extractStructure() { + const rows = document.querySelectorAll('table tbody tr'); + const regionali = []; + let currentRegionale = null; + let currentLocale = null; + + rows.forEach(row => { + const cells = row.querySelectorAll('td'); + const numCells = cells.length; + + // Il nome è sempre nella cella con classe "grassetto" + let nome = ''; + cells.forEach(td => { + if (td.className.trim() === 'grassetto') { + nome = td.innerText.trim(); + } + }); + + if (!nome) return; + + if (numCells === 4) { + // Comitato Regionale + currentRegionale = { nome, comitati_locali: [] }; + regionali.push(currentRegionale); + currentLocale = null; + } else if (numCells === 5) { + // Comitato Locale + currentLocale = { nome, sottosedi: [] }; + if (currentRegionale) { + currentRegionale.comitati_locali.push(currentLocale); + } + } else if (numCells >= 6) { + // Unità / Sottosede + if (currentLocale) { + currentLocale.sottosedi.push(nome); + } + } + }); + + // Se c'è un solo regionale, restituisce l'oggetto direttamente (non array) + return regionali.length === 1 ? regionali[0] : regionali; + } + + const $btn = $(` + + `); + + $btn.on('click', function () { + const structure = extractStructure(); + const json = JSON.stringify(structure, null, 2); + + navigator.clipboard.writeText(json).then(() => { + const locali = Array.isArray(structure) + ? structure.reduce((acc, r) => acc + r.comitati_locali.length, 0) + : structure.comitati_locali.length; + const sottosedi = Array.isArray(structure) + ? structure.reduce((acc, r) => acc + r.comitati_locali.reduce((a, l) => a + l.sottosedi.length, 0), 0) + : structure.comitati_locali.reduce((a, l) => a + l.sottosedi.length, 0); + + console.log("GAIA Swissknife: JSON struttura sedi copiato in clipboard."); + console.log(` Locali: ${locali}, Sottosedi: ${sottosedi}`); + + const orig = $(this).text(); + $(this).text(`✓ Copiato! (${locali} locali, ${sottosedi} sottosedi)`).css('background-color', '#28a745'); + setTimeout(() => $(this).text(orig).css('background-color', '#5a3d8a'), 3000); + }).catch(err => { + console.error("GAIA Swissknife: Errore copia clipboard:", err); + alert("Errore durante la copia in clipboard."); + }); + }); + + // Inserisce in cima alla sidebar, nella sezione Swissknife + if ($('#sezione').length > 0) { + appendToSwissknifeSection( + $('
  • ').append($btn) + ); + } + } + $(document).ready(() => { applyStyleFixes(); addExamOutcomeButtons(); @@ -592,21 +821,35 @@ GM_addStyle(GM_getResourceText("DataTablesCSS")); addAuthorizationProfileTool(); enhanceCurriculumTable(); highlightRejectedCourses(); + addSediPreferitaTool(); + addCopyCatalogButton(); + addCopyStructureButton(); console.log("GAIA Swissknife: Funzioni caricate con successo!"); }); })(jQuery.noConflict(true)); // ==CHANGELOG== +// v1.1.009 - Nuove funzioni: +// - Aggiunto pulsante "Copia catalogo corsi (JSON)" nella sidebar di /courses/catalog/ +// - Estrae nome, codice, livello, tipo, obiettivo per ogni corso e copia il JSON in clipboard +// - Aggiunto tool "Sede preferita" nelle pagine con selezione sedi: select persistente (GM_setValue/getValue), +// pulsante "Solo preferito" (deseleziona tutto, seleziona il comitato scelto + figli), pulsante "Seleziona tutto" +// - Pulsanti Swissknife ora posizionati in cima alla sidebar con header e separatore +// v1.1.008 - Nuova funzione: +// - Aggiunto pulsante "Copia struttura sedi (JSON)" nel menu laterale della pagina /us/elenchi/volontari/ +// - Estrae la gerarchia Comitato Regionale > Comitato Locale > Sottosedi dalla tabella sedi +// - Copia il JSON in clipboard con feedback visivo; se presente un solo Regionale restituisce l'oggetto diretto, altrimenti un array +// - Visibile solo quando la tabella sedi è presente (non nella vista risultati con iframe) +// v1.0.4 - Fix: +// - Modificata regex per il matching dei codici corso, compatibile con i codici che contengono caratteri alfanumerici +// v1.0.2 - Fix: +// - Modificata regex per il matching dei codici corso, compatibile con i codici che contengono spazi +// - Aggiunto link per aprire la pagina del corso nella modale dove compaiono i corsi rilevati // v1.0.0 - Versione stabile, con: // - DataTable integrata nella tabella curriculum // - Evidenziazione "Respinto" // - Correzioni grafiche file/link // - Gestione richieste iscrizioni con modale mobile // - Funzioni esiti massivi su termina corso -// v1.0.2 - Fix: -// - Modificata regex per il matching dei codici corso, compatibile con i codici che contengono spazi -// - Aggiunto link per aprire la pagina del corso nella modale dove compaiono i corsi rilevati -// v1.0.4 - Fix: -// - Modificata regex per il matching dei codici corso, compatibile con i codici che contengono caratteri alfanumerici // ==/CHANGELOG== \ No newline at end of file