Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ export default async function createConfig(): Promise<Config> {
organizationName: 'marcofarina', // Usually your GitHub org/username.
projectName: 'python-doesnt-byte', // Usually your repo name.
deploymentBranch: 'gh-pages',
trailingSlash: false,
// true: ogni pagina esce come `<pagina>/index.html` e gli URL canonici
// finiscono con `/`. È la forma che GitHub Pages serve da sé: `/pagina/`
// risponde 200 e `/pagina` riceve un 301 verso `/pagina/` conservando la
// query. Con false la build scriveva `pagina.html` e `/pagina/` finiva in
// 404.html: un lampo di «pagina non trovata» prima del redirect client-side.
trailingSlash: true,

onBrokenLinks: 'throw',

Expand Down Expand Up @@ -115,14 +120,30 @@ export default async function createConfig(): Promise<Config> {
// Analytics privacy-first (GoatCounter): nessun cookie, niente dati personali.
// Solo in produzione: in dev (`npm start`) NODE_ENV è 'development', così le
// visite locali non gonfiano le statistiche.
scripts:
//
// Con trailingSlash: true i path arrivano come `/pagina/`; fino alla 0.20
// erano `/pagina`. `goatcounter.path` toglie la barra finale (non quella
// della home, che l'ha sempre avuta) così lo storico resta su una riga sola
// per pagina. count.js lo legge al caricamento: lo script inline deve
// precedere count.js, per questo stanno tutti e due qui, nell'ordine.
headTags:
process.env.NODE_ENV === 'production'
? [
{
src: 'https://gc.zgo.at/count.js',
async: true,
'data-goatcounter':
'https://pythondoesntbyte.goatcounter.com/count',
tagName: 'script',
attributes: {},
innerHTML:
'window.goatcounter={path:function(p){return p.replace(' +
'/^(\\/python-doesnt-byte\\/[^?#]*[^\\/?#])\\/(?=[?#]|$)/,"$1")}};',
},
{
tagName: 'script',
attributes: {
src: 'https://gc.zgo.at/count.js',
async: 'async',
'data-goatcounter':
'https://pythondoesntbyte.goatcounter.com/count',
},
},
]
: [],
Expand Down
21 changes: 17 additions & 4 deletions plugins/copy-page-md/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
parseMarkdownFile,
DEFAULT_PARSE_FRONT_MATTER,
} = require('@docusaurus/utils');
const { applyTrailingSlash } = require('@docusaurus/utils-common');

const PLUGIN_NAME = 'copy-page-md';

Expand Down Expand Up @@ -129,8 +130,12 @@ async function toCleanMarkdown(absPath, doc, context, archivio) {
removeContentTitle: true,
});

const siteUrl = context.siteConfig.url.replace(/\/$/, '');
const pageUrl = siteUrl + doc.permalink;
const { url, baseUrl, trailingSlash } = context.siteConfig;
// doc.permalink non porta la barra finale: la aggiunge il router. La «Fonte»
// cita l'URL canonico, quello di sitemap e <link rel="canonical">.
const pageUrl =
url.replace(/\/$/, '') +
applyTrailingSlash(doc.permalink, { trailingSlash, baseUrl });
const body = sanitize(content, context.siteDir, archivio);

return (
Expand All @@ -142,7 +147,9 @@ async function toCleanMarkdown(absPath, doc, context, archivio) {

// Percorso di output del .md, coerente col fetch lato client. Il permalink
// include il baseUrl: lo si toglie perché outDir è già la radice servita a
// baseUrl. Permalink con `/` finale (landing/category) → index.md.
// baseUrl. Permalink con `/` finale (landing/category) → index.md. Con
// trailingSlash: true la pagina è `lezione/index.html` e il .md resta accanto
// alla cartella (`lezione.md`): il permalink dei dati non ha la barra finale.
function outFile(outDir, permalink, baseUrl) {
let rel = permalink.startsWith(baseUrl)
? permalink.slice(baseUrl.length)
Expand Down Expand Up @@ -199,8 +206,14 @@ module.exports = function copyPageMd(context) {
const file = outFile(outDir, permalink, siteConfig.baseUrl);
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, md, 'utf-8');
// Copia .txt per gli assistenti AI: GitHub Pages serve i .md come
// text/markdown, che lo strumento di navigazione di ChatGPT rifiuta;
// i .txt escono come text/plain; charset=utf-8.
fs.writeFileSync(file.replace(/\.md$/, '.txt'), md, 'utf-8');
}
console.log(`[${PLUGIN_NAME}] scritti ${manifest.length} file .md`);
console.log(
`[${PLUGIN_NAME}] scritti ${manifest.length} file .md (+ copia .txt)`,
);
},
};
};
6 changes: 4 additions & 2 deletions src/components/CopyPageButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ function CopyPageMenu({ permalink }: { permalink: string }): ReactNode {
// siteConfig.url): così punta sempre all'host da cui la pagina è
// realmente servita — dominio custom, fallback GitHub Pages o deploy di
// preview — invece di una sola URL canonica fissata a build-time.
const mdUrl = window.location.origin + relMdUrl;
const target = url + encodeURIComponent(buildAiPrompt(mdUrl));
// Si cita la copia .txt (stesso contenuto del .md): GitHub Pages serve i
// .md come text/markdown, che il browsing di ChatGPT rifiuta.
const txtUrl = window.location.origin + relMdUrl.replace(/\.md$/, '.txt');
const target = url + encodeURIComponent(buildAiPrompt(txtUrl));
window.open(target, '_blank', 'noopener,noreferrer');
setOpen(false);
},
Expand Down
7 changes: 4 additions & 3 deletions src/components/CopyPageButton/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ export const AI_PROVIDERS: readonly AiProvider[] = [

/**
* Prompt italiano coerente col tono di `DEFAULT_EXPLAIN_PROMPT` del PyRunner.
* Cita l'URL del `.md` e chiede una spiegazione adatta a uno studente.
* Cita l'URL del testo ripulito (la copia `.txt` del `.md`) e chiede una
* spiegazione adatta a uno studente.
*/
export function buildAiPrompt(mdUrl: string): string {
export function buildAiPrompt(pageUrl: string): string {
return (
'Sto studiando dal manuale scolastico «Python Doesn’t Byte». ' +
'Leggi questa pagina e spiegamela in modo semplice, come a uno studente di ' +
'15 anni alle prime armi: dammi un riassunto chiaro e nomina i concetti ' +
'chiave. Rispondi in italiano.\n\n' +
mdUrl
pageUrl
);
}
6 changes: 4 additions & 2 deletions src/pages/percorso.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ function StudentCard({
const { code, setCode, clear } = useCurriculum();
const [draft, setDraft] = useState('');
const [error, setError] = useState<string | null>(null);
const startUrl = useBaseUrl('/programmatore');
const startUrl = useBaseUrl('/programmatore/');

const activate = () => {
try {
Expand Down Expand Up @@ -363,7 +363,9 @@ export default function PercorsoPage(): ReactNode {
const data = useCurriculumPluginData();
const { code } = useCurriculum();
const { siteConfig } = useDocusaurusContext();
const percorsoUrl = useBaseUrl('/percorso');
// Con la barra finale: è l'URL canonico, così il link condiviso e il QR non
// passano da un 301.
const percorsoUrl = useBaseUrl('/percorso/');

const allKeys = useMemo(
() => data.volumes.flatMap((v) => collectKeys(v.tree)),
Expand Down
4 changes: 3 additions & 1 deletion src/pages/support/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ function QRModal({
background: T.dark ? '#161619' : '#ffffff',
border: `1px solid ${T.border}`,
borderRadius: 'var(--radius-lg)',
padding: '30px 26px 26px',
/* top ≥ top+altezza del bottone Chiudi (12+32) + respiro: il QR
non deve finire sotto la ×. */
padding: '56px 26px 26px',
textAlign: 'center',
boxShadow: '0 24px 60px rgba(0,0,0,0.4)',
}}
Expand Down
2 changes: 1 addition & 1 deletion src/theme/PyRunner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function PyRunnerInner(props: PyRunnerProps) {
const editorRef = useRef<EditorHandle | null>(null);
const cleanupRef = useRef<(() => void) | null>(null);
const toastTimerRef = useRef<number | undefined>(undefined);
const playgroundUrl = useBaseUrl('/playground');
const playgroundUrl = useBaseUrl('/playground/');

const maxLines = coerceNumber(props.maxLines, 20);
const readonly = coerceBool(props.readonly);
Expand Down
52 changes: 26 additions & 26 deletions static/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,61 +11,61 @@ Apprendista). Al momento il volume più completo è il «Manuale dell'Artefice»
esempi Python eseguibili direttamente nel browser (runtime Brython) e quiz.

Questo file è mantenuto a mano: se una voce non corrisponde più al sito, fa fede
la sitemap (https://www.rainbowbits.cloud/sitemap.xml).
la sitemap (https://rainbowbits.cloud/python-doesnt-byte/sitemap.xml).

## Orientamento

- [Perché Python](https://www.rainbowbits.cloud/python-doesnt-byte/perche-python): il manifesto del progetto — perché questo libro esiste e per chi.
- [Introduzione](https://www.rainbowbits.cloud/python-doesnt-byte/docs/intro): punto di partenza del libro.
- [Come usare questo libro](https://www.rainbowbits.cloud/python-doesnt-byte/docs/come-usare-questo-libro): convenzioni tipografiche, elementi interattivi e suggerimenti.
- [Perché Python](https://rainbowbits.cloud/python-doesnt-byte/perche-python/): il manifesto del progetto — perché questo libro esiste e per chi.
- [Introduzione](https://rainbowbits.cloud/python-doesnt-byte/docs/intro/): punto di partenza del libro.
- [Come usare questo libro](https://rainbowbits.cloud/python-doesnt-byte/docs/come-usare-questo-libro/): convenzioni tipografiche, elementi interattivi e suggerimenti.

## Volume 1 — Manuale del Programmatore

> I fondamenti del linguaggio. In costruzione.

- [Manuale del Programmatore](https://www.rainbowbits.cloud/python-doesnt-byte/programmatore): introduzione al volume.
- [Le variabili](https://www.rainbowbits.cloud/python-doesnt-byte/programmatore/variabili): primo contatto con le variabili in Python.
- [Manuale del Programmatore](https://rainbowbits.cloud/python-doesnt-byte/programmatore/): introduzione al volume.
- [Le variabili](https://rainbowbits.cloud/python-doesnt-byte/programmatore/variabili/): primo contatto con le variabili in Python.

## Volume 2 — Manuale dell'Artefice (OOP)

> Programmazione a oggetti, costruita passo-passo attorno al lettore musicale «Risonanza». È il volume più completo.

- [Manuale dell'Artefice](https://www.rainbowbits.cloud/python-doesnt-byte/artefice): introduzione al volume.
- [Perché gli oggetti?](https://www.rainbowbits.cloud/python-doesnt-byte/artefice/perche-gli-oggetti): i limiti del codice procedurale e l'idea-madre dell'OOP — tenere insieme dati e comportamento.
- [Classi, istanze e metodi](https://www.rainbowbits.cloud/python-doesnt-byte/artefice/classi-e-istanze): lo stampo e l'esemplare; cosa fa __init__, perché self compare ovunque, attributi contro metodi.
- [Mostrare un oggetto](https://www.rainbowbits.cloud/python-doesnt-byte/artefice/mostrare-un-oggetto): i metodi speciali __str__ e __repr__ — l'oggetto che impara a raccontarsi.
- [Metodi di classe e statici](https://www.rainbowbits.cloud/python-doesnt-byte/artefice/metodi-di-classe-e-statici): attributi di classe, @classmethod (e i costruttori alternativi), @staticmethod.
- [Incapsulamento](https://www.rainbowbits.cloud/python-doesnt-byte/artefice/incapsulamento): interfaccia pubblica contro stato interno, convenzioni _ e __, «tell, don't ask».
- [Manuale dell'Artefice](https://rainbowbits.cloud/python-doesnt-byte/artefice/): introduzione al volume.
- [Perché gli oggetti?](https://rainbowbits.cloud/python-doesnt-byte/artefice/perche-gli-oggetti/): i limiti del codice procedurale e l'idea-madre dell'OOP — tenere insieme dati e comportamento.
- [Classi, istanze e metodi](https://rainbowbits.cloud/python-doesnt-byte/artefice/classi-e-istanze/): lo stampo e l'esemplare; cosa fa __init__, perché self compare ovunque, attributi contro metodi.
- [Mostrare un oggetto](https://rainbowbits.cloud/python-doesnt-byte/artefice/mostrare-un-oggetto/): i metodi speciali __str__ e __repr__ — l'oggetto che impara a raccontarsi.
- [Metodi di classe e statici](https://rainbowbits.cloud/python-doesnt-byte/artefice/metodi-di-classe-e-statici/): attributi di classe, @classmethod (e i costruttori alternativi), @staticmethod.
- [Incapsulamento](https://rainbowbits.cloud/python-doesnt-byte/artefice/incapsulamento/): interfaccia pubblica contro stato interno, convenzioni _ e __, «tell, don't ask».

## Volume 3 — Manuale dell'Archivista

> Basi di dati e SQL, con query eseguibili in pagina. In costruzione.

- [Manuale dell'Archivista](https://www.rainbowbits.cloud/python-doesnt-byte/archivista): introduzione al volume.
- [Manuale dell'Archivista](https://rainbowbits.cloud/python-doesnt-byte/archivista/): introduzione al volume.

## Volume 4 — Biblioteca dell'Apprendista

> Esercizi assegnati dai volumi precedenti. In costruzione.

- [Biblioteca dell'Apprendista](https://www.rainbowbits.cloud/python-doesnt-byte/apprendista): introduzione al volume.
- [Biblioteca dell'Apprendista](https://rainbowbits.cloud/python-doesnt-byte/apprendista/): introduzione al volume.

## Le basi del linguaggio (materiale fondamenti)

- [Programmare in Python](https://www.rainbowbits.cloud/python-doesnt-byte/docs/fondamenti-di-python/programmare-in-python): cosa significa programmare e come gira il codice Python.
- [Le variabili](https://www.rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/le-variabili)
- [Input](https://www.rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/input)
- [Output](https://www.rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/output)
- [Le stringhe](https://www.rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/le-stringhe)
- [I moduli](https://www.rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/moduli)
- [Programmare in Python](https://rainbowbits.cloud/python-doesnt-byte/docs/fondamenti-di-python/programmare-in-python/): cosa significa programmare e come gira il codice Python.
- [Le variabili](https://rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/le-variabili/)
- [Input](https://rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/input/)
- [Output](https://rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/output/)
- [Le stringhe](https://rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/le-stringhe/)
- [I moduli](https://rainbowbits.cloud/python-doesnt-byte/docs/basi-del-linguaggio/moduli/)

## Strumenti

- [Playground](https://www.rainbowbits.cloud/python-doesnt-byte/playground): editor Python a tutta pagina, esecuzione nel browser.
- [Note di rilascio](https://www.rainbowbits.cloud/python-doesnt-byte/note-di-rilascio): changelog del progetto.
- [Playground](https://rainbowbits.cloud/python-doesnt-byte/playground/): editor Python a tutta pagina, esecuzione nel browser.
- [Note di rilascio](https://rainbowbits.cloud/python-doesnt-byte/note-di-rilascio/): changelog del progetto.

## Optional

- [Sostieni il progetto](https://www.rainbowbits.cloud/python-doesnt-byte/support): donazioni «value for value».
- [Licenza](https://www.rainbowbits.cloud/python-doesnt-byte/legale/licenza): CC BY-NC-SA 4.0.
- [Privacy policy](https://www.rainbowbits.cloud/python-doesnt-byte/legale/privacy-policy)
- [Termini di servizio](https://www.rainbowbits.cloud/python-doesnt-byte/legale/termini-di-servizio)
- [Sostieni il progetto](https://rainbowbits.cloud/python-doesnt-byte/support/): donazioni «value for value».
- [Licenza](https://rainbowbits.cloud/python-doesnt-byte/legale/licenza/): CC BY-NC-SA 4.0.
- [Privacy policy](https://rainbowbits.cloud/python-doesnt-byte/legale/privacy-policy/)
- [Termini di servizio](https://rainbowbits.cloud/python-doesnt-byte/legale/termini-di-servizio/)
14 changes: 0 additions & 14 deletions static/robots.txt

This file was deleted.

Loading