gaia-swissknife/tampermonkey-script/GAIA-Swissknife.user.js

855 lines
No EOL
39 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name GAIA - Swissknife
// @namespace https://gitlab.luigidacunto.com/consultant/gaia-swissknife
// @updateURL 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
// @author Luigi D'Acunto
// @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
// @require https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js
// @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==
GM_addStyle(GM_getResourceText("DataTablesCSS"));
(function($) {
'use strict';
console.log("GAIA Swissknife: Inizializzazione in corso!");
// Recupera la versione dallo script se disponibile, altrimenti usa un valore di fallback
const currentVersion = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : 'x.x';
console.log(`GAIA Swissknife: Versione ${currentVersion}`);
// Inizializza le funzionalità
// Funzione per aggiungere il blocco di intestazione in cima alla sidebar
function ensureSwissknifeHeader() {
if ($('#sezione').length === 0 || $('#gaia-swissknife-header').length > 0) return;
console.log("GAIA Swissknife: Aggiungo intestazione GAIA Swissknife in cima...");
const $header = $(`<li id="gaia-swissknife-header" class="dropdown-header grassetto piu-grande">GAIA Swissknife v${currentVersion}</li>`);
const $divider = $('<hr id="gaia-swissknife-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 <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
function fixAutorizzazioniSidebarFilters() {
const $ul = $("#sezione-2");
if (!$ul.length) return;
const voci = {};
// Scansiona tutti i link
$ul.find("li[role='presentation'] a").each(function () {
const $a = $(this);
const text = $a.clone().children().remove().end().text().trim();
const badge = parseInt($a.find(".badge").text().trim(), 10);
const href = $a.attr("href");
if (!text || isNaN(badge)) return;
if (!voci[text]) {
voci[text] = {
count: 0,
href: href
};
}
voci[text].count += badge;
});
$ul.find("li[role='presentation']").remove();
Object.entries(voci).forEach(([tipo, info]) => {
const $li = $(`
<li role="presentation">
<a href="${info.href}">
${tipo}
<span class="badge pull-right">${info.count}</span>
</a>
</li>
`);
$ul.append($li);
});
}
// Funzione per ricostruire le celle con i dettagli delle qualifiche nel curriculum in modo pulito
function rebuildCurriculumDetailCells() {
$("td.piu-piccolo").each(function (cellIndex) {
const $td = $(this);
const rawNodes = Array.from(this.childNodes);
const nodes = rawNodes.filter(n => {
if (n.nodeType === 3) return n.nodeValue.trim() !== "";
return true;
});
console.log(`\n📦 [Cella ${cellIndex + 1}] Analizzo ${nodes.length} nodi validi...`);
const newContent = $('<div></div>');
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.nodeType === 1 && node.tagName === "I") {
const $icon = $(node).clone();
const $row = $('<div class="gaia-line"></div>').css({
"display": "flex",
"align-items": "baseline",
"gap": "5px",
"line-height": "1.4",
"margin-bottom": "2px"
});
$icon.css({
"margin-right": "4px",
"vertical-align": "middle"
});
$row.append($icon);
let content = null;
let nextIndex = i + 1;
while (nextIndex < nodes.length) {
const nextNode = nodes[nextIndex];
if (nextNode.nodeType === 3) {
const text = nextNode.nodeValue.trim();
if (text) {
content = $('<span></span>').text(text);
if (text.length > 40) {
content.css({
"display": "inline-block",
"max-width": "200px",
"white-space": "nowrap",
"overflow": "hidden",
"text-overflow": "ellipsis",
"vertical-align": "middle"
}).attr("title", text);
}
break;
}
} else if (nextNode.nodeType === 1) {
const $el = $(nextNode);
if (nextNode.tagName === "A") {
let text = $el.text().trim();
if (!text) {
const href = $el.attr("href") || "";
const fallback = href.split("/").pop();
text = fallback || "(documento)";
$el.text(text);
}
$el.css({
"display": "inline-block",
"max-width": "200px",
"white-space": "nowrap",
"overflow": "hidden",
"text-overflow": "ellipsis",
"vertical-align": "middle",
"color": "#a94442"
}).attr("title", text);
content = $el;
break;
}
else if (nextNode.tagName === "SPAN") {
const text = $el.text().trim();
if (text) {
content = $el.clone();
content.css({
"display": "inline-block",
"max-width": "200px",
"white-space": "nowrap",
"overflow": "hidden",
"text-overflow": "ellipsis",
"vertical-align": "middle"
}).attr("title", text);
break;
}
}
}
nextIndex++;
}
i = nextIndex - 1;
if (content) {
$row.append(content);
newContent.append($row);
console.log(`✅ Riga generata: ${$icon.attr("class")} + contenuto`);
} else {
console.warn(`⚠️ Riga ignorata: icona "${$icon.attr("class")}" senza contenuto`);
}
}
}
$td.empty().append(newContent);
});
}
// Funzione per applicare le correzioni di stile alle celle del curriculum
function applyStyleFixes() {
const url = window.location.href;
// Correzione per le celle del curriculum
if (url.match(/\/profilo\/\d+\/curriculum\//)) {
console.log("GAIA Swissknife: Ricostruzione celle curriculum in modo ordinato...");
rebuildCurriculumDetailCells();
}
// Correzione per menu laterale autorizzazioni
if (url.includes("/autorizzazioni")) {
console.log("GAIA Swissknife: Correzione menu laterale autorizzazioni...");
fixAutorizzazioniSidebarFilters();
}
}
// Funzione per aggiungere i pulsanti per settare gli esiti degli esami
function addExamOutcomeButtons() {
console.log("GAIA Swissknife: esecuzione di addExamOutcomeButtons() ...");
// Verifica che ci siano i select che gestiscono gli esiti
if ($("select[id^='id_part_'][id$='-ammissione']").length > 0) {
// Cerca il menu laterale (ul#sezione) dove inserire il blocco
if ($('#sezione').length > 0) {
console.log("GAIA Swissknife: Elementi trovati, aggiungo i pulsanti nel menu laterale...");
// Aggiungi i pulsanti per settare gli esiti
[
{ 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(
$('<li role="presentation"></li>').append(
$(`<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>`)
)
);
});
// Evento per il pulsante "Tutti Ammessi/Esito Positivo x2"
$('#button_set_ampp').click(() => {
$("select[id^='id_part_'][id$='-ammissione']").val("AM").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_1']").val("P").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_2']").val("P").trigger('change');
console.log("GAIA Swissknife: Valori impostati - Ammessi, Positivo, Positivo");
});
// Evento per il pulsante "Ammessi, Positivo, Non previsto"
$('#button_set_ampo').click(() => {
$("select[id^='id_part_'][id$='-ammissione']").val("AM").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_1']").val("P").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_2']").val("O").trigger('change');
console.log("GAIA Swissknife: Valori impostati - Ammessi, Positivo, Non previsto");
});
// Evento per il pulsante "Ammessi, Non previsto, Positivo"
$('#button_set_amop').click(() => {
$("select[id^='id_part_'][id$='-ammissione']").val("AM").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_1']").val("O").trigger('change');
$("select[id^='id_part_'][id$='-esito_parte_2']").val("P").trigger('change');
console.log("GAIA Swissknife: Valori impostati - Ammessi, Non previsto, Positivo");
});
} else {
console.log("GAIA Swissknife: Elemento ul#sezione non trovato.");
}
} else {
console.log("GAIA Swissknife: Nessun elemento trovato, i pulsanti non verranno mostrati.");
}
}
// Funzione per aggiungere il pulsante "Apri tutti i Curriculum" sezione iscritti di un corso
function addOpenAllProfilesButton() {
if (!window.location.href.includes('/iscritti/')) return;
const openButton = $(`
<button id="open_all_profiles" style="margin: 5px; padding: 8px; width: 97%; background-color:rgba(234, 0, 255, 0.65); color: #FFF; border: none; border-radius: 4px; cursor: pointer;">
Apri tutti i Curriculum
</button>
`);
openButton.on('click', () => {
const iframe = document.querySelector('iframe.embed-responsive-item');
if (!iframe) {
alert("Impossibile trovare l'iframe con la lista.");
console.log("GAIA Swissknife: iframe non trovato.");
return;
}
// Verifica che il contenuto sia accessibile (same-origin)
let iframeDoc;
try {
iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
} catch (err) {
alert("Accesso all'iframe bloccato per motivi di sicurezza (cross-origin).");
console.error("GAIA Swissknife: accesso iframe negato.", err);
return;
}
const $iframeLinks = $(iframeDoc).find("a[href^='/profilo/'][href*='?fpc']");
if ($iframeLinks.length === 0) {
alert("Nessun profilo trovato all'interno dell'iframe.");
console.log("GAIA Swissknife: Nessun link profilo nell'iframe.");
return;
}
const profileLinks = $iframeLinks.map(function() {
const href = $(this).attr('href');
const match = href.match(/\/profilo\/(\d+)\//);
if (match && match[1]) {
const id = match[1];
return `https://gaia.cri.it/profilo/${id}/curriculum/?us`;
}
return null;
}).get().filter(Boolean);
// Mostra le URL in console sempre
console.log("GAIA Swissknife: Profili trovati:");
profileLinks.forEach(link => console.log(link));
if (!confirm(`Vuoi aprire ${profileLinks.length} profili in nuove schede?`)) return;
profileLinks.forEach(link => {
window.open(link, '_blank');
});
console.log(`GAIA Swissknife: Aperte ${profileLinks.length} schede da iframe.`);
});
// Inserimento del pulsante nel menu laterale o in floating
if ($('#sezione').length > 0) {
appendToSwissknifeSection(
$('<li role="presentation"></li>').append(openButton)
);
} else {
openButton.css({
position: 'fixed',
top: '10px',
right: '10px',
zIndex: 9999
}).appendTo('body');
}
}
// Funzione per evidenziare le righe con "Respinto"
function highlightRejectedCourses() {
if (!window.location.href.match(/\/profilo\/\d+\/curriculum\//)) return;
console.log("GAIA Swissknife: Avvio evidenziazione 'Respinto'…");
const $table = $(".table.table-striped");
if (!$table.length || !$.fn.DataTable.isDataTable($table)) return;
const api = $table.DataTable();
function applyRejectionStyle() {
// Prima puliamo tutte le righe (reset)
api.rows().every(function () {
const $row = $(this.node());
$row.css({
"box-shadow": "",
"background-color": ""
});
});
// Poi coloriamo solo le righe che contengono <span class="text-danger">Respinto</span>
$table.find("span.text-danger").each(function () {
const text = $(this).text().trim().toLowerCase();
if (text === "respinto") {
const $row = $(this).closest("tr");
console.log("🟥 Evidenzio riga:", $row.index(), $row.find("p.grassetto").text().trim());
$row.css({
"box-shadow": "inset 8px 0 0 0 #dc3545",
"background-color": "#fbe9ea"
});
}
});
}
applyRejectionStyle();
$table.on("draw.dt", applyRejectionStyle);
}
// Funzione per aggiungere il tool per i profili delle richieste iscrizioni
function addAuthorizationProfileTool() {
if (!window.location.href.includes("/autorizzazioni/")) return;
console.log("GAIA Swissknife: Attivo tool per gestione richieste iscrizione...");
const richieste = [];
$(".panel.panel-default").each(function () {
const $panel = $(this);
const $corsoLink = $panel.find("a[href^='/aspirante/corso-base/']");
if ($corsoLink.length === 0) return; // blocco non relativo a richiesta corso
const corsoText = $corsoLink.text().trim();
const match = corsoText.match(/([A-Z]{2,}\/\d{4}\/[A-Z0-9\-]+(?: [A-Z0-9\-]+)?\/\d+)/);
if (!match) return;
const codiceCorso = match[1];
const $profileLink = $panel.find("a[href^='/profilo/']").first();
const nome = $profileLink.text().trim();
const href = $profileLink.attr("href");
const idMatch = href.match(/\/profilo\/(\d+)\//);
if (idMatch && idMatch[1]) {
richieste.push({
codice: codiceCorso,
nome,
id: idMatch[1],
linkCorso: $corsoLink.attr("href"),
$panel
});
}
});
const corsiUnici = [...new Set(richieste.map(r => r.codice))];
if (corsiUnici.length === 0) return;
// === CREAZIONE MODALE ===
const $modal = $(`
<div id="gaia-auth-tool" style="position: fixed; top: 20px; right: 20px; width: 320px; background: white; border: 2px solid #007BFF; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 9999; overflow: hidden;">
<div id="gaia-auth-header" style="background-color: #007BFF; color: white; padding: 8px 10px; font-weight: bold; cursor: move; display: flex; justify-content: space-between; align-items: center;">
<span>🛠 GAIA Swissknife Richieste iscrizione</span>
<button id="gaia-auth-toggle" title="Riduci/Espandi" style="background: transparent; border: none; color: white; font-weight: bold; font-size: 16px; cursor: pointer; padding: 0; margin-left: 10px;"></button>
</div>
<div id="gaia-auth-body" style="padding: 15px;">
<div id="gaia-auth-summary" style="margin-bottom: 10px; font-size: 13px;">
<strong>📋 Richieste trovate:</strong><br>
${corsiUnici.map(c => {
const matching = richieste.filter(r => r.codice === c);
const count = matching.length;
const plural = count === 1 ? "richiesta" : "richieste";
const courseUrl = matching[0]?.linkCorso ? `https://gaia.cri.it${matching[0].linkCorso}` : null;
const label = courseUrl
? `<a href="${courseUrl}" target="_blank">${c}</a> ${count} ${plural}`
: `${c} ${count} ${plural}`;
return `${label}`;
}).join("<br>")}
</div>
<label for="gaia-auth-select">Seleziona un corso:</label>
<select id="gaia-auth-select" style="width: 100%; padding: 5px; margin-bottom: 10px;">
<option value="">-- Seleziona --</option>
${corsiUnici.map(c => `<option value="${c}">${c}</option>`).join("")}
</select>
<button id="gaia-auth-btn" style="width: 100%; padding: 8px; background-color: #007BFF; color: white; border: none; border-radius: 4px; cursor: pointer;">Recupera Profili</button>
<div id="gaia-auth-openall-container" style="display: none; margin-top: 10px;">
<button id="gaia-auth-openall" style="width: 100%; padding: 6px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">Apri tutti i Curriculum</button>
</div>
<div id="gaia-auth-results" style="margin-top: 10px; max-height: 200px; overflow-y: auto; border-top: 1px solid #ddd; padding-top: 10px;"></div>
</div>
</div>
`);
$('body').append($modal);
// Riduci / Espandi
let isCollapsed = false;
$('#gaia-auth-toggle').on('click', function (e) {
isCollapsed = !isCollapsed;
$('#gaia-auth-body').slideToggle(200);
$(this).text(isCollapsed ? '+' : '');
e.stopPropagation();
});
// Modal mobile
let isDragging = false, dragOffsetX = 0, dragOffsetY = 0;
$('#gaia-auth-header').on('mousedown', function (e) {
const rect = document.getElementById('gaia-auth-tool').getBoundingClientRect();
isDragging = true;
dragOffsetX = e.clientX - rect.left;
dragOffsetY = e.clientY - rect.top;
e.preventDefault();
});
$(document).on('mousemove', function (e) {
if (!isDragging) return;
const $modal = $('#gaia-auth-tool');
let left = e.clientX - dragOffsetX;
let top = e.clientY - dragOffsetY;
left = Math.max(0, Math.min(left, window.innerWidth - $modal.outerWidth()));
top = Math.max(0, Math.min(top, window.innerHeight - $modal.outerHeight()));
$modal.css({ left: left + 'px', top: top + 'px', right: 'auto' });
}).on('mouseup', () => isDragging = false);
// Recupera profili
$('#gaia-auth-btn').on('click', function () {
const codiceSelezionato = $('#gaia-auth-select').val();
const $results = $('#gaia-auth-results').empty();
$('#gaia-auth-openall-container').hide();
if (!codiceSelezionato) {
alert("Seleziona un codice corso.");
return;
}
// Reset evidenziazione
$(".panel.panel-default").css("background-color", "");
const trovati = richieste.filter(r => r.codice === codiceSelezionato);
if (trovati.length === 0) {
$results.append(`<div style="color: red;">Nessun risultato trovato.</div>`);
return;
}
trovati.forEach(r => {
r.$panel.css("background-color", "#d4edda");
const link = `https://gaia.cri.it/profilo/${r.id}/curriculum/?us`;
$results.append(`<div><a href="${link}" target="_blank">${r.nome}</a></div>`);
});
$('#gaia-auth-openall-container').show();
$('#gaia-auth-openall').off('click').on('click', function () {
trovati.forEach(r => window.open(`https://gaia.cri.it/profilo/${r.id}/curriculum/?us`, '_blank'));
});
});
}
// Funzione per migliorare la tabella del curriculum
function enhanceCurriculumTable() {
if (!window.location.href.match(/\/profilo\/\d+\/curriculum\//)) return;
const palette = {
"Qualifica CRI": "#e6f0ff", // blu chiaro
"Altra Qualifica": "#e6fff0", // verde chiaro
"Esperienze Professionali": "#fff3e6", // arancione chiaro
"Competenza Personale": "#e6faff", // ciano chiaro
"Patente Civile": "#f0f0f0", // grigio chiaro
"Patente CRI": "#f3e6ff" // lilla chiaro
};
const $table = $(".table.table-striped");
const $tbody = $table.find("tbody");
if (!$table.length || !$tbody.length) return;
console.log("GAIA Swissknife: DataTable + Colorazione permanente per tipologia...");
$table.DataTable({
paging: false,
order: [[0, 'asc'], [1, 'asc']],
columnDefs: [
{ orderable: false, targets: 2 }
],
language: {
search: "Filtra qualifiche:",
zeroRecords: "Nessuna qualifica trovata",
info: "Mostrate _TOTAL_ qualifiche",
infoEmpty: "Nessuna qualifica disponibile",
infoFiltered: "(filtrate da _MAX_ totali)"
}
});
function applyColoring() {
const tableApi = $table.DataTable();
tableApi.rows().every(function () {
const $row = $(this.node());
const $tipoCell = $row.find("td").eq(0);
const tipo = $tipoCell.text().trim();
if (palette[tipo]) {
$tipoCell.css("background-color", palette[tipo]);
}
});
}
// Applica le correzioni le colorazioni dei tipi di qualifica in lista
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(() => {
applyStyleFixes();
addExamOutcomeButtons();
addOpenAllProfilesButton();
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
// ==/CHANGELOG==