diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d45aebe..a769e97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,8 @@ jobs: const fs = require('fs'); const html = fs.readFileSync('docs/index.html', 'utf-8'); const host = [...html.matchAll(/https?:\/\/([a-zA-Z0-9.-]+)/g)].map(m => m[1]); - const ammessi = new Set(['github.com', 'www.w3.org']); + // overleaf.com: la sezione che spiega come compilare il PDF lΓ . + const ammessi = new Set(['github.com', 'www.w3.org', 'www.overleaf.com']); const estranei = [...new Set(host)].filter(h => !ammessi.has(h)); if (estranei.length) { console.error('domini inattesi:', estranei); process.exit(1); } console.log('Nessun dominio esterno inatteso'); diff --git a/README.md b/README.md index c14dbdc..e22bb82 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,34 @@ Il sorgente Γ¨ un `.tex` **vero** β€” stesso documento, due output diversi. | comando | `\numerica{valore}{tolleranza}` | Risposta numerica libera | | comando | `\risultato` | Placeholder punteggio | +## 🌍 Il PDF si compila anche su Overleaf + +Il sorgente Γ¨ LaTeX vero: per il **PDF** non serve texforge. Lo stesso `.tex` compila +su [Overleaf](https://www.overleaf.com) β€” comodo se sei su un computer della scuola, +su tablet, o se devi lavorare in due sullo stesso documento. + +1. Su Overleaf: **New Project β†’ Blank Project** (oppure *Upload Project* se hai giΓ  uno ZIP). +2. Carica (**Upload**) nel progetto due file: il tuo `.tex` e **`quizstruct.sty`** + ([scaricalo qui](latex-source/quizstruct.sty), o prendilo dagli allegati di una + [release](https://github.com/Francy2009/TeXForge/releases/latest)). + Il `.sty` va nella **stessa cartella** del `.tex`: Overleaf lo trova da solo, non + serve installare niente. +3. Compilatore **pdfLaTeX** (`Menu β†’ Compiler`), poi **Recompile**. + +`quizstruct.sty` usa solo pacchetti standard (`amsmath`, `amssymb`, `xcolor`, +`enumitem`, `pgffor`), giΓ  presenti nella TeX Live di Overleaf. + +**Non servono** `quiz.cfg`, `quiz.js` e `quiz.css`: quelli riguardano solo l'HTML. + +### E l'HTML? + +L'**HTML interattivo** β€” quello con le risposte cliccabili e il punteggio che si +calcola da solo β€” Γ¨ la parte in piΓΉ, e **si ottiene solo con l'app**: richiede +`htlatex` piΓΉ i file di configurazione, che Overleaf non mette in conto. + +In pratica: su Overleaf ottieni il compito da stampare, con texforge ottieni **anche** +la versione che si corregge da sola. Stesso sorgente, nessuna modifica. + ## πŸ“ Struttura ``` diff --git a/desktop/latex.js b/desktop/latex.js index ac6cc5f..aa7ace0 100644 --- a/desktop/latex.js +++ b/desktop/latex.js @@ -99,8 +99,43 @@ function cartelleBin(radice) { .filter((d) => fs.statSync(d).isDirectory()); } -function nomeEseguibile(nome) { - return PIATTAFORMA === 'win32' ? `${nome}.exe` : nome; +// Su Windows non tutti i comandi di TeX Live sono .exe: tlmgr, per dirne uno, +// Γ¨ tlmgr.bat. Cercare solo .exe lo rende invisibile e la distribuzione +// sembra non gestibile. Proviamo quindi tutti i suffissi eseguibili. +const SUFFISSI_ESEGUIBILE = PIATTAFORMA === 'win32' ? ['.exe', '.bat', '.cmd', ''] : ['']; + +function trovaEseguibile(dir, nome) { + for (const suffisso of SUFFISSI_ESEGUIBILE) { + const candidato = path.join(dir, nome + suffisso); + if (fs.existsSync(candidato)) return candidato; + } + return null; +} + +// spawn/execFile non sanno eseguire uno script .bat o .cmd: vanno passati a +// cmd.exe. La riga viene composta a mano e citata perchΓ© i percorsi possono +// contenere spazi (C:\Users\Nome Cognome\...). +// +// Attenzione: cmd non ha modo di rappresentare una virgoletta dentro una +// stringa citata. Raddoppiarla non la rende letterale, chiude e riapre le +// virgolette, e da lΓ¬ in poi &, | e > tornano metacaratteri attivi: sarebbe +// esecuzione di comandi arbitrari. Un argomento che le contiene non Γ¨ quindi +// esprimibile e viene rifiutato, invece di essere citato male. +function comandoPiattaforma(eseguibile, argomenti = [], opzioni = {}) { + if (PIATTAFORMA !== 'win32' || !/\.(bat|cmd)$/i.test(eseguibile)) { + return { comando: eseguibile, argomenti, opzioni }; + } + const pezzi = [eseguibile, ...argomenti].map(String); + const insidioso = pezzi.find((a) => /["\r\n]/.test(a)); + if (insidioso !== undefined) { + throw new Error(`argomento non ammesso per un comando Windows: ${insidioso}`); + } + const riga = pezzi.map((a) => `"${a}"`).join(' '); + return { + comando: process.env.COMSPEC || 'cmd.exe', + argomenti: ['/d', '/s', '/c', `"${riga}"`], + opzioni: { ...opzioni, windowsVerbatimArguments: true }, + }; } // --------------------------------------------------------------------------- @@ -122,7 +157,8 @@ async function esisteNelPath(nome) { async function versioneDi(eseguibile) { try { - const { stdout } = await execFileAsync(eseguibile, ['--version'], { timeout: 15000 }); + const c = comandoPiattaforma(eseguibile, ['--version'], { timeout: 15000 }); + const { stdout } = await execFileAsync(c.comando, c.argomenti, c.opzioni); return String(stdout).split('\n')[0].trim(); } catch { return null; @@ -134,8 +170,8 @@ async function versioneDi(eseguibile) { export async function rilevaTeX(userDataDir) { const gestita = radiceGestita(userDataDir); for (const dir of cartelleBin(gestita)) { - const pdflatex = path.join(dir, nomeEseguibile('pdflatex')); - if (fs.existsSync(pdflatex)) { + const pdflatex = trovaEseguibile(dir, 'pdflatex'); + if (pdflatex) { const versione = await versioneDi(pdflatex); if (versione) { return { @@ -144,10 +180,8 @@ export async function rilevaTeX(userDataDir) { radice: gestita, versione, pdflatex, - htlatex: fs.existsSync(path.join(dir, nomeEseguibile('htlatex'))) - ? path.join(dir, nomeEseguibile('htlatex')) : null, - tlmgr: fs.existsSync(path.join(dir, nomeEseguibile('tlmgr'))) - ? path.join(dir, nomeEseguibile('tlmgr')) : null, + htlatex: trovaEseguibile(dir, 'htlatex'), + tlmgr: trovaEseguibile(dir, 'tlmgr'), }; } } @@ -402,17 +436,19 @@ function ambiente(tex) { async function tlmgr(tex, argomenti, { onLog, timeout = 300000 } = {}) { if (!tex?.tlmgr) throw new Error('tlmgr non disponibile in questa distribuzione'); - return eseguiConLog(tex.tlmgr, argomenti, { env: ambiente(tex), timeout }, onLog); + const c = comandoPiattaforma(tex.tlmgr, argomenti, { env: ambiente(tex), timeout }); + return eseguiConLog(c.comando, c.argomenti, c.opzioni, onLog); } async function tlmgrOutput(tex, argomenti, timeout = 120000) { if (!tex?.tlmgr) throw new Error('tlmgr non disponibile in questa distribuzione'); - const { stdout } = await execFileAsync(tex.tlmgr, argomenti, { + const c = comandoPiattaforma(tex.tlmgr, argomenti, { env: ambiente(tex), timeout, maxBuffer: 32 * 1024 * 1024, windowsHide: true, }); + const { stdout } = await execFileAsync(c.comando, c.argomenti, c.opzioni); return stdout; } @@ -432,7 +468,9 @@ export async function pacchettiInstallati(tex) { // Cerca un pacchetto fra quelli disponibili. export async function cercaPacchetto(tex, termine) { - const pulito = String(termine || '').trim(); + // Il termine arriva da un campo di testo e finisce in una riga di comando: + // teniamo solo i caratteri che compaiono davvero nei nomi dei pacchetti. + const pulito = String(termine || '').trim().replace(/[^A-Za-z0-9._+\- ]/g, ''); if (!pulito) return []; // --global cerca nel database remoto, non solo fra gli installati. const out = await tlmgrOutput(tex, ['search', '--global', '--file', `${pulito}`]).catch( @@ -491,19 +529,20 @@ export async function compilaPdf(tex, cartella, stem, onLog) { env: { ...ambiente(tex), openin_any: 'p', openout_any: 'p' }, timeout: 120000, }; + const c = comandoPiattaforma(tex.pdflatex, argomenti, opzioni); // Due passate: la seconda risolve riferimenti e indice. - await eseguiConLog(tex.pdflatex, argomenti, opzioni, onLog); - await eseguiConLog(tex.pdflatex, argomenti, opzioni, onLog); + await eseguiConLog(c.comando, c.argomenti, c.opzioni, onLog); + await eseguiConLog(c.comando, c.argomenti, c.opzioni, onLog); return path.join(cartella, `${stem}.pdf`); } export async function compilaHtml(tex, cartella, stem, cfgStem, onLog) { if (!tex.htlatex) throw new Error('htlatex non disponibile'); - const argomenti = cfgStem ? [stem, cfgStem] : [stem]; - await eseguiConLog(tex.htlatex, argomenti, { + const c = comandoPiattaforma(tex.htlatex, cfgStem ? [stem, cfgStem] : [stem], { cwd: cartella, env: { ...ambiente(tex), openin_any: 'p', openout_any: 'p' }, timeout: 120000, - }, onLog); + }); + await eseguiConLog(c.comando, c.argomenti, c.opzioni, onLog); return path.join(cartella, `${stem}.html`); } diff --git a/desktop/main.js b/desktop/main.js index 4fb5968..1e61ecf 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -155,6 +155,11 @@ ipcMain.handle('installa-tex', async () => { log(`\nAvviso: alcuni pacchetti di base non sono stati installati: ${e.message}\n`); }); tex = await latex.rilevaTeX(userData); + } else { + // Senza tlmgr non si installa nulla: meglio dirlo che chiudere con un + // "Pronto" che nasconde una distribuzione a metΓ . + log('\nAvviso: tlmgr non trovato nella distribuzione appena installata: ' + + 'i pacchetti di base non sono stati aggiunti e la pagina Pacchetti resterΓ  inattiva.\n'); } log('\nPronto.\n'); diff --git a/desktop/renderer/app.js b/desktop/renderer/app.js index 896bc3d..a73ae24 100644 --- a/desktop/renderer/app.js +++ b/desktop/renderer/app.js @@ -246,7 +246,11 @@ async function caricaInstallati() { } if (!stato.tex.tlmgr) { $('avvisoPkg').hidden = false; - $('avvisoPkg').innerHTML = 'Questa distribuzione non Γ¨ gestibile da texforge. Stai usando il LaTeX di sistema: installa i pacchetti con il gestore della tua distribuzione, oppure lascia che texforge installi la propria copia da Motore LaTeX.'; + // Il motivo cambia con l'origine: dire "stai usando il LaTeX di sistema" + // a chi ha installato la copia di texforge manda fuori strada. + $('avvisoPkg').innerHTML = stato.tex.tipo === 'gestita' + ? 'Questa distribuzione non Γ¨ gestibile da texforge. La copia installata da texforge non contiene tlmgr: prova Reinstalla la distribuzione da Motore LaTeX.' + : 'Questa distribuzione non Γ¨ gestibile da texforge. Stai usando il LaTeX di sistema: installa i pacchetti con il gestore della tua distribuzione, oppure lascia che texforge installi la propria copia da Motore LaTeX.'; elenco.innerHTML = '
  • β€”
  • '; return; } diff --git a/docs/index.html b/docs/index.html index d450414..261308e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -29,6 +29,7 @@