Aggiorna la versione a 1.1.009 e aggiunge nuove funzionalità: pulsante per copiare il catalogo corsi in JSON, tool per la selezione della sede preferita, e miglioramenti nell'ordinamento dei pulsanti nella sidebar.

This commit is contained in:
Luigi D'Acunto 2026-04-17 16:22:08 +02:00
parent d23bee3a33
commit 8d88454abe
2 changed files with 285 additions and 40 deletions

4
.gitignore vendored
View file

@ -1,2 +1,4 @@
_ignored _ignored
.venv .venv
.playwright-mcp
.vscode

View file

@ -5,7 +5,7 @@
// @downloadURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/GAIA-Swissknife.user.js // @downloadURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/GAIA-Swissknife.user.js
// @changelogURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/CHANGELOG.md // @changelogURL https://luigidacunto.com/scripts/gaia-swissknife/tampermonkey-script/CHANGELOG.md
// @author Luigi D'Acunto // @author Luigi D'Acunto
// @version 1.1.006 // @version 1.1.009
// @description Aggiunge funzionalità alle pagine di GAIA // @description Aggiunge funzionalità alle pagine di GAIA
// @match https://gaia.cri.it/* // @match https://gaia.cri.it/*
// @require https://code.jquery.com/jquery-3.6.0.min.js // @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 // @resource DataTablesCSS https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css
// @grant GM_addStyle // @grant GM_addStyle
// @grant GM_getResourceText // @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-idle // @run-at document-idle
// ==/UserScript== // ==/UserScript==
@ -29,18 +31,21 @@ GM_addStyle(GM_getResourceText("DataTablesCSS"));
// Inizializza le funzionalità // Inizializza le funzionalità
// Funzione per aggiungere il blocco di intestazione // Funzione per aggiungere il blocco di intestazione in cima alla sidebar
function ensureSwissknifeHeader() { function ensureSwissknifeHeader() {
console.log("GAIA Swissknife: Aggiungo intestazione GAIA Swissknife..."); if ($('#sezione').length === 0 || $('#gaia-swissknife-header').length > 0) return;
if ($('#sezione').length > 0 && $('#gaia-swissknife-header').length === 0) { console.log("GAIA Swissknife: Aggiungo intestazione GAIA Swissknife in cima...");
const headerHTML = ` const $header = $(`<li id="gaia-swissknife-header" class="dropdown-header grassetto piu-grande">GAIA Swissknife v${currentVersion}</li>`);
<hr> const $divider = $('<hr id="gaia-swissknife-divider">');
<li id="gaia-swissknife-header" class="dropdown-header grassetto piu-grande"> // Prepend: header prima, poi divider — risultato: [header][divider][voci esistenti]
GAIA Swissknife v${currentVersion} // I pulsanti vengono inseriti tra header e divider
</li> $('#sezione').prepend($divider).prepend($header);
`; }
$('#sezione').append(headerHTML);
} // Helper: inserisce un <li> 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 // Funzione per correggere i filtri della sidebar delle autorizzazioni
@ -231,26 +236,18 @@ GM_addStyle(GM_getResourceText("DataTablesCSS"));
if ($('#sezione').length > 0) { if ($('#sezione').length > 0) {
console.log("GAIA Swissknife: Elementi trovati, aggiungo i pulsanti nel menu laterale..."); console.log("GAIA Swissknife: Elementi trovati, aggiungo i pulsanti nel menu laterale...");
ensureSwissknifeHeader();
// Aggiungi i pulsanti per settare gli esiti // Aggiungi i pulsanti per settare gli esiti
const buttonsHTML = ` [
<li role="presentation"> { id: 'button_set_ampp', label: 'Ammessi, Positivo, Positivo', color: '#007BFF' },
<button id="button_set_ampp" style="margin: 5px; padding: 8px; width: 97%; background-color: #007BFF; color: #FFF; border: none; border-radius: 4px; cursor: pointer;"> { id: 'button_set_ampo', label: 'Ammessi, Positivo, Non previsto', color: '#28a745' },
Ammessi, Positivo, Positivo { id: 'button_set_amop', label: 'Ammessi, Non previsto, Positivo', color: 'rgb(167, 40, 99)' }
</button> ].forEach(({ id, label, color }) => {
</li> appendToSwissknifeSection(
<li role="presentation"> $('<li role="presentation"></li>').append(
<button id="button_set_ampo" style="margin: 5px; padding: 8px; width: 97%; background-color: #28a745; color: #FFF; border: none; border-radius: 4px; cursor: pointer;"> $(`<button id="${id}" type="button" style="margin:5px;padding:8px;width:97%;background-color:${color};color:#FFF;border:none;border-radius:4px;cursor:pointer;">${label}</button>`)
Ammessi, Positivo, Non previsto )
</button> );
</li> });
<li role="presentation">
<button id="button_set_amop" style="margin: 5px; padding: 8px; width: 97%; background-color:rgb(167, 40, 99); color: #FFF; border: none; border-radius: 4px; cursor: pointer;">
Ammessi, Non previsto, Positivo
</button>
</li>
`;
$('#sezione').append(buttonsHTML);
// Evento per il pulsante "Tutti Ammessi/Esito Positivo x2" // Evento per il pulsante "Tutti Ammessi/Esito Positivo x2"
$('#button_set_ampp').click(() => { $('#button_set_ampp').click(() => {
@ -344,9 +341,7 @@ GM_addStyle(GM_getResourceText("DataTablesCSS"));
// Inserimento del pulsante nel menu laterale o in floating // Inserimento del pulsante nel menu laterale o in floating
if ($('#sezione').length > 0) { if ($('#sezione').length > 0) {
ensureSwissknifeHeader(); appendToSwissknifeSection(
$('#sezione').append(
$('<li role="presentation"></li>').append(openButton) $('<li role="presentation"></li>').append(openButton)
); );
} else { } else {
@ -585,6 +580,240 @@ GM_addStyle(GM_getResourceText("DataTablesCSS"));
applyColoring(); 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 = $('<select id="gaia-preferred-sede-select"></select>').css({
width: '100%', padding: '4px', margin: '4px 5px 4px 5px',
fontSize: '12px', boxSizing: 'border-box', width: 'calc(100% - 10px)'
});
$select.append('<option value="">-- Comitato preferito --</option>');
sediMap.forEach(({ value, label, numCells }) => {
const prefix = numCells === 4 ? '' : numCells === 5 ? '\u00a0\u00a0\u00a0' : '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0';
const $opt = $(`<option value="${value}">${prefix}${label}</option>`);
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 = $('<button type="button" style="margin:2px 5px;padding:6px;width:calc(100% - 10px);background-color:#e67e00;color:#FFF;border:none;border-radius:4px;cursor:pointer;font-size:12px;">Solo preferito</button>');
$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 = $('<button type="button" style="margin:2px 5px 6px 5px;padding:6px;width:calc(100% - 10px);background-color:#6c757d;color:#FFF;border:none;border-radius:4px;cursor:pointer;font-size:12px;">Seleziona tutto</button>');
$btnAll.on('click', function () {
$('input[name="sedi"]').prop('checked', true);
console.log('GAIA Swissknife: Tutte le sedi selezionate.');
});
appendToSwissknifeSection(
$('<li role="presentation"></li>').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 = $('<button type="button" style="margin: 5px; padding: 8px; width: 97%; background-color: #c0504d; color: #FFF; border: none; border-radius: 4px; cursor: pointer;">Copia catalogo corsi (JSON)</button>');
$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($('<li role="presentation"></li>').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 = $(`
<button id="gaia-copy-structure-btn" type="button" style="margin: 5px; padding: 8px; width: 97%; background-color: #5a3d8a; color: #FFF; border: none; border-radius: 4px; cursor: pointer;">
Copia struttura sedi (JSON)
</button>
`);
$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(
$('<li role="presentation"></li>').append($btn)
);
}
}
$(document).ready(() => { $(document).ready(() => {
applyStyleFixes(); applyStyleFixes();
addExamOutcomeButtons(); addExamOutcomeButtons();
@ -592,21 +821,35 @@ GM_addStyle(GM_getResourceText("DataTablesCSS"));
addAuthorizationProfileTool(); addAuthorizationProfileTool();
enhanceCurriculumTable(); enhanceCurriculumTable();
highlightRejectedCourses(); highlightRejectedCourses();
addSediPreferitaTool();
addCopyCatalogButton();
addCopyStructureButton();
console.log("GAIA Swissknife: Funzioni caricate con successo!"); console.log("GAIA Swissknife: Funzioni caricate con successo!");
}); });
})(jQuery.noConflict(true)); })(jQuery.noConflict(true));
// ==CHANGELOG== // ==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: // v1.0.0 - Versione stabile, con:
// - DataTable integrata nella tabella curriculum // - DataTable integrata nella tabella curriculum
// - Evidenziazione "Respinto" // - Evidenziazione "Respinto"
// - Correzioni grafiche file/link // - Correzioni grafiche file/link
// - Gestione richieste iscrizioni con modale mobile // - Gestione richieste iscrizioni con modale mobile
// - Funzioni esiti massivi su termina corso // - 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== // ==/CHANGELOG==