create-openclass-uniminuto 1.6.1 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-openclass-uniminuto.mjs +333 -59
- package/package.json +1 -1
- package/template/.github/workflows/deploy.yml +2 -2
- package/template/README.md +477 -149
- package/template/demo_semana1.md +1 -1
- package/template/demo_semana2.md +1 -1
- package/template/demo_semana3.md +1 -1
- package/template/demo_semana4.md +1 -1
- package/template/demo_semana5.md +1 -1
- package/template/demo_semana6.md +1 -1
- package/template/demo_semana7.md +1 -1
- package/template/demo_semana8.md +1 -1
- package/template/package-lock.json +11568 -11568
- package/template/package.json +63 -63
- package/template/plantillas/launcher.md +1 -1
- package/template/plantillas/semana.md +214 -88
- package/template/scripts/decks.mjs +13 -4
- package/template/scripts/dev-all.mjs +20 -7
- package/template/scripts/generar-desde-config.mjs +312 -48
- package/template/slides.md +1 -1
- package/template/theme/uniminuto/styles/base.css +9 -8
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uso principal:
|
|
5
5
|
* npm run config
|
|
6
|
+
* npm run config -- --force
|
|
6
7
|
* node scripts/generar-desde-config.mjs --config config/openclass.config.iot-ejemplo.json --force
|
|
8
|
+
*
|
|
9
|
+
* Principio:
|
|
10
|
+
* - openclass.config.json es la fuente de verdad.
|
|
11
|
+
* - generation.activeWeeks define qué semanas aparecen en el portal, exportaciones y deploy.
|
|
12
|
+
* - No sobrescribe el contenido académico en semanas/*.md salvo con --force o overwriteWeekContent=true.
|
|
7
13
|
*/
|
|
14
|
+
|
|
8
15
|
import fs from "node:fs";
|
|
9
16
|
import path from "node:path";
|
|
10
17
|
|
|
@@ -13,8 +20,10 @@ const args = process.argv.slice(2);
|
|
|
13
20
|
function valueAfter(flag, fallback) {
|
|
14
21
|
const i = args.indexOf(flag);
|
|
15
22
|
if (i >= 0 && args[i + 1]) return args[i + 1];
|
|
23
|
+
|
|
16
24
|
const pair = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
17
25
|
if (pair) return pair.slice(flag.length + 1);
|
|
26
|
+
|
|
18
27
|
return fallback;
|
|
19
28
|
}
|
|
20
29
|
|
|
@@ -22,7 +31,18 @@ const configPath = valueAfter("--config", "openclass.config.json");
|
|
|
22
31
|
const force = args.includes("--force") || args.includes("-f");
|
|
23
32
|
const dryRun = args.includes("--dry-run");
|
|
24
33
|
const forceCleanDemo = args.includes("--clean-demo");
|
|
25
|
-
|
|
34
|
+
|
|
35
|
+
const FALLBACK_FAVICON_PNG_BASE64 =
|
|
36
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
|
|
37
|
+
|
|
38
|
+
const REQUIRED_THEME_ASSETS = [
|
|
39
|
+
"public/fondos/slide-01-portada.png",
|
|
40
|
+
"public/fondos/slide-05-template.png",
|
|
41
|
+
"public/fondos/slide-06-cierre.png",
|
|
42
|
+
"public/imagenes/avion.png",
|
|
43
|
+
"public/imagenes/favicon.png",
|
|
44
|
+
"public/favicon.png",
|
|
45
|
+
];
|
|
26
46
|
|
|
27
47
|
function fail(message) {
|
|
28
48
|
console.error(`\n❌ ${message}\n`);
|
|
@@ -30,7 +50,10 @@ function fail(message) {
|
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
function readJson(filePath) {
|
|
33
|
-
if (!fs.existsSync(filePath))
|
|
53
|
+
if (!fs.existsSync(filePath)) {
|
|
54
|
+
fail(`No se encontró el archivo de configuración: ${filePath}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
try {
|
|
35
58
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
36
59
|
} catch (error) {
|
|
@@ -67,6 +90,7 @@ function writeBinaryFileIfMissing(filePath, bufferOrBase64, { label = filePath }
|
|
|
67
90
|
const buffer = Buffer.isBuffer(bufferOrBase64)
|
|
68
91
|
? bufferOrBase64
|
|
69
92
|
: Buffer.from(bufferOrBase64, "base64");
|
|
93
|
+
|
|
70
94
|
fs.writeFileSync(filePath, buffer);
|
|
71
95
|
}
|
|
72
96
|
|
|
@@ -76,8 +100,10 @@ function writeBinaryFileIfMissing(filePath, bufferOrBase64, { label = filePath }
|
|
|
76
100
|
|
|
77
101
|
function removeFileIfExists(filePath, { label = filePath } = {}) {
|
|
78
102
|
if (!fs.existsSync(filePath)) return false;
|
|
103
|
+
|
|
79
104
|
if (!dryRun) fs.rmSync(filePath, { force: true });
|
|
80
105
|
console.log(`${dryRun ? "🔎" : "🧹"} Eliminado: ${label}`);
|
|
106
|
+
|
|
81
107
|
return true;
|
|
82
108
|
}
|
|
83
109
|
|
|
@@ -94,44 +120,66 @@ function asArray(value) {
|
|
|
94
120
|
return Array.isArray(value) ? value : [];
|
|
95
121
|
}
|
|
96
122
|
|
|
123
|
+
function uniqueNumbers(values) {
|
|
124
|
+
return [...new Set(values.map(Number))]
|
|
125
|
+
.filter((value) => Number.isInteger(value))
|
|
126
|
+
.sort((a, b) => a - b);
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
function replaceTokens(template, tokens) {
|
|
98
|
-
return Object.entries(tokens).reduce(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
130
|
+
return Object.entries(tokens).reduce((content, [key, value]) => {
|
|
131
|
+
return content.replaceAll(`{{${key}}}`, String(value ?? ""));
|
|
132
|
+
}, template);
|
|
102
133
|
}
|
|
103
134
|
|
|
104
135
|
function readTemplate(filePath, fallback) {
|
|
105
136
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : fallback;
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
function normalizeBase(value) {
|
|
109
|
-
let base = value || "/";
|
|
110
|
-
if (!base.startsWith("/")) base = `/${base}`;
|
|
111
|
-
if (!base.endsWith("/")) base = `${base}/`;
|
|
112
|
-
return base;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
139
|
const config = readJson(configPath);
|
|
116
140
|
const course = config.course || {};
|
|
117
141
|
const generation = config.generation || {};
|
|
118
142
|
|
|
119
|
-
const courseShort = cleanShortName(
|
|
120
|
-
|
|
143
|
+
const courseShort = cleanShortName(
|
|
144
|
+
course.shortName || config.shortName || config.courseCode || "curso",
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const courseName = String(
|
|
148
|
+
course.fullName || course.name || config.courseName || "Nombre del curso",
|
|
149
|
+
).trim();
|
|
150
|
+
|
|
121
151
|
const courseYear = String(course.year || config.year || new Date().getFullYear()).trim();
|
|
122
|
-
|
|
152
|
+
|
|
153
|
+
const description = String(
|
|
154
|
+
course.description || config.description || "Agrega aquí la descripción general del curso.",
|
|
155
|
+
).trim();
|
|
156
|
+
|
|
123
157
|
const openClassLabel = String(course.openClassLabel || "Open Class").trim();
|
|
124
|
-
|
|
158
|
+
|
|
159
|
+
const weeksTotal = Number(
|
|
160
|
+
generation.weeksTotal ||
|
|
161
|
+
config.weeksTotal ||
|
|
162
|
+
config.totalWeeks ||
|
|
163
|
+
asArray(config.weeks).length ||
|
|
164
|
+
8,
|
|
165
|
+
);
|
|
125
166
|
|
|
126
167
|
if (!courseShort) fail("El campo course.shortName es obligatorio.");
|
|
127
168
|
if (!courseName) fail("El campo course.fullName es obligatorio.");
|
|
128
|
-
if (!Number.isInteger(weeksTotal) || weeksTotal < 1)
|
|
169
|
+
if (!Number.isInteger(weeksTotal) || weeksTotal < 1) {
|
|
170
|
+
fail("generation.weeksTotal debe ser un número entero mayor o igual a 1.");
|
|
171
|
+
}
|
|
129
172
|
|
|
130
|
-
const configuredWeeks = new Map(
|
|
173
|
+
const configuredWeeks = new Map(
|
|
174
|
+
asArray(config.weeks)
|
|
175
|
+
.filter((week) => Number.isInteger(Number(week.number)))
|
|
176
|
+
.map((week) => [Number(week.number), week]),
|
|
177
|
+
);
|
|
131
178
|
|
|
132
179
|
function weekInfo(number) {
|
|
133
180
|
const week = configuredWeeks.get(number) || {};
|
|
134
181
|
const title = String(week.title || `Título semana ${number}`).trim();
|
|
182
|
+
|
|
135
183
|
return {
|
|
136
184
|
number,
|
|
137
185
|
title,
|
|
@@ -144,12 +192,17 @@ function weekInfo(number) {
|
|
|
144
192
|
}
|
|
145
193
|
|
|
146
194
|
const allWeeks = Array.from({ length: weeksTotal }, (_, i) => weekInfo(i + 1));
|
|
147
|
-
|
|
195
|
+
|
|
196
|
+
const activeWeeksRaw = asArray(generation.activeWeeks ?? config.activeWeeks);
|
|
197
|
+
|
|
148
198
|
const activeWeekNumbers = activeWeeksRaw.length
|
|
149
|
-
? activeWeeksRaw
|
|
150
|
-
: allWeeks
|
|
199
|
+
? uniqueNumbers(activeWeeksRaw).filter((number) => number >= 1 && number <= weeksTotal)
|
|
200
|
+
: allWeeks
|
|
201
|
+
.filter((week) => week.status !== "draft" && week.status !== "inactive")
|
|
202
|
+
.map((week) => week.number);
|
|
151
203
|
|
|
152
204
|
const activeWeeks = allWeeks.filter((week) => activeWeekNumbers.includes(week.number));
|
|
205
|
+
|
|
153
206
|
const overwriteLaunchers = generation.overwriteLaunchers !== false || force;
|
|
154
207
|
const overwritePortal = generation.overwritePortal !== false || force;
|
|
155
208
|
const overwriteDecks = generation.overwriteDecks !== false || force;
|
|
@@ -159,12 +212,49 @@ const exportPortal = generation.exportPortal === true;
|
|
|
159
212
|
|
|
160
213
|
const semanaTemplate = readTemplate(
|
|
161
214
|
"plantillas/semana.md",
|
|
162
|
-
|
|
215
|
+
`---
|
|
216
|
+
layout: slide-01-portada
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
::title::
|
|
220
|
+
{{COURSE_NAME}}
|
|
221
|
+
|
|
222
|
+
::week::
|
|
223
|
+
Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
|
|
224
|
+
|
|
225
|
+
::date::
|
|
226
|
+
{{WEEK_DATE}}
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
layout: slide-08-titulo-texto
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
::title::
|
|
233
|
+
Tema central
|
|
234
|
+
|
|
235
|
+
::content::
|
|
236
|
+
{{WEEK_THEME}}
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
layout: slide-12-cierre
|
|
240
|
+
---
|
|
241
|
+
`,
|
|
163
242
|
);
|
|
164
243
|
|
|
165
244
|
const launcherTemplate = readTemplate(
|
|
166
245
|
"plantillas/launcher.md",
|
|
167
|
-
|
|
246
|
+
`---
|
|
247
|
+
theme: ./theme/uniminuto
|
|
248
|
+
title: {{COURSE_NAME}} — Semana {{WEEK_NUMBER}} — {{WEEK_TITLE}}
|
|
249
|
+
favicon: /favicon.png
|
|
250
|
+
codeCopy: true
|
|
251
|
+
transition: fade
|
|
252
|
+
routerMode: hash
|
|
253
|
+
drawings:
|
|
254
|
+
persist: false
|
|
255
|
+
src: ./semanas/{{COURSE_SHORT}}_semana{{WEEK_NUMBER}}.md
|
|
256
|
+
---
|
|
257
|
+
`,
|
|
168
258
|
);
|
|
169
259
|
|
|
170
260
|
function weekTokens(week) {
|
|
@@ -184,29 +274,142 @@ function weekTokens(week) {
|
|
|
184
274
|
|
|
185
275
|
function portalWeekItem(week) {
|
|
186
276
|
const slug = `${courseShort}_semana${week.number}`;
|
|
187
|
-
|
|
277
|
+
|
|
278
|
+
return `### **Semana ${week.number}**
|
|
279
|
+
|
|
280
|
+
<a href="./semanas/${slug}/#/1" target="_self">${week.title}</a>
|
|
281
|
+
|
|
282
|
+
<a href="./descargas/${slug}.pdf" download>Descargar PDF</a> · <a href="./descargas/${slug}.pptx" download>Descargar PPTX</a>`;
|
|
188
283
|
}
|
|
189
284
|
|
|
190
285
|
function buildPortal() {
|
|
286
|
+
const midpoint = Math.ceil(activeWeeks.length / 2);
|
|
287
|
+
|
|
191
288
|
const left = activeWeeks.length
|
|
192
|
-
? activeWeeks
|
|
289
|
+
? activeWeeks
|
|
290
|
+
.filter((_, index) => index < midpoint)
|
|
291
|
+
.map(portalWeekItem)
|
|
292
|
+
.join("\n\n")
|
|
193
293
|
: "### Sin semanas activas\n\nEjecuta `npm run semana -- 1` para generar la primera semana.";
|
|
194
|
-
const right = activeWeeks.filter((_, index) => index >= Math.ceil(activeWeeks.length / 2)).map(portalWeekItem).join("\n\n") || "### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
|
|
195
294
|
|
|
196
|
-
|
|
295
|
+
const right =
|
|
296
|
+
activeWeeks
|
|
297
|
+
.filter((_, index) => index >= midpoint)
|
|
298
|
+
.map(portalWeekItem)
|
|
299
|
+
.join("\n\n") ||
|
|
300
|
+
"### Próximamente\n\nActiva más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.";
|
|
301
|
+
|
|
302
|
+
return `---
|
|
303
|
+
theme: ./theme/uniminuto
|
|
304
|
+
title: ${courseName} — ${openClassLabel}
|
|
305
|
+
favicon: /favicon.png
|
|
306
|
+
codeCopy: true
|
|
307
|
+
transition: fade
|
|
308
|
+
routerMode: hash
|
|
309
|
+
drawings:
|
|
310
|
+
persist: false
|
|
311
|
+
layout: slide-01-portada
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
::title::
|
|
315
|
+
${courseName}
|
|
316
|
+
|
|
317
|
+
::week::
|
|
318
|
+
${openClassLabel}
|
|
319
|
+
|
|
320
|
+
::date::
|
|
321
|
+
${courseYear}
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
layout: slide-08-titulo-texto
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
::title::
|
|
328
|
+
Descripción general del curso
|
|
329
|
+
|
|
330
|
+
::content::
|
|
331
|
+
${description}
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
layout: slide-08-titulo-texto
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
::title::
|
|
338
|
+
Ruta de aprendizaje
|
|
339
|
+
|
|
340
|
+
::content::
|
|
341
|
+
El curso se organiza en semanas. Cada semana cuenta con un lanzador raíz y un archivo interno en la carpeta \`semanas/\`.
|
|
342
|
+
|
|
343
|
+
Las presentaciones activas se controlan desde \`openclass.config.json\` mediante el campo \`generation.activeWeeks\`.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
layout: slide-10-titulo-dos-columnas
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
::title::
|
|
350
|
+
Presentaciones disponibles
|
|
351
|
+
|
|
352
|
+
::left::
|
|
353
|
+
${left}
|
|
354
|
+
|
|
355
|
+
::right::
|
|
356
|
+
${right}
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
layout: slide-12-cierre
|
|
360
|
+
---
|
|
361
|
+
`;
|
|
197
362
|
}
|
|
198
363
|
|
|
199
364
|
function buildDecks() {
|
|
200
365
|
const entries = [
|
|
201
|
-
`function normalizeBase(value) {
|
|
366
|
+
`function normalizeBase(value) {
|
|
367
|
+
let base = value || "/";
|
|
368
|
+
|
|
369
|
+
if (!base.startsWith("/")) {
|
|
370
|
+
base = \`/\${base}\`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!base.endsWith("/")) {
|
|
374
|
+
base = \`\${base}/\`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return base;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const SITE_BASE = normalizeBase(process.env.SITE_BASE || "/");
|
|
381
|
+
|
|
382
|
+
function withBase(value = "") {
|
|
383
|
+
return \`\${SITE_BASE}\${value.replace(/^[/]+/, "")}\`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export const decks = [
|
|
387
|
+
{
|
|
388
|
+
name: "openclass-${courseShort}",
|
|
389
|
+
entry: "slides.md",
|
|
390
|
+
out: "dist",
|
|
391
|
+
base: SITE_BASE,
|
|
392
|
+
exportable: ${exportPortal ? "true" : "false"},
|
|
393
|
+
},`,
|
|
202
394
|
];
|
|
203
395
|
|
|
204
396
|
for (const week of activeWeeks) {
|
|
205
397
|
const slug = `${courseShort}_semana${week.number}`;
|
|
206
|
-
|
|
398
|
+
|
|
399
|
+
entries.push(` {
|
|
400
|
+
name: "${slug}",
|
|
401
|
+
entry: "${slug}.md",
|
|
402
|
+
out: "dist/semanas/${slug}",
|
|
403
|
+
base: withBase("semanas/${slug}/"),
|
|
404
|
+
exportable: true,
|
|
405
|
+
},`);
|
|
207
406
|
}
|
|
208
407
|
|
|
209
|
-
entries.push(`]
|
|
408
|
+
entries.push(`];
|
|
409
|
+
|
|
410
|
+
export default decks;
|
|
411
|
+
`);
|
|
412
|
+
|
|
210
413
|
return entries.join("\n");
|
|
211
414
|
}
|
|
212
415
|
|
|
@@ -217,7 +420,7 @@ function buildScripts() {
|
|
|
217
420
|
semana: "node scripts/semana.mjs",
|
|
218
421
|
"pages:check": "node scripts/preparar-github-pages.mjs",
|
|
219
422
|
"actualizar:tema": "npm create openclass-uniminuto@latest . -- --update-theme",
|
|
220
|
-
"actualizar:tema:preview": "npm run actualizar:tema && npm run dev",
|
|
423
|
+
"actualizar:tema:preview": "npm run actualizar:tema && npm run config && npm run dev",
|
|
221
424
|
};
|
|
222
425
|
|
|
223
426
|
for (let i = 1; i <= weeksTotal; i++) {
|
|
@@ -226,27 +429,38 @@ function buildScripts() {
|
|
|
226
429
|
}
|
|
227
430
|
|
|
228
431
|
scripts.clean = "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"";
|
|
229
|
-
scripts["clean:downloads"] =
|
|
230
|
-
|
|
432
|
+
scripts["clean:downloads"] =
|
|
433
|
+
"node -e \"require('fs').rmSync('public/descargas',{recursive:true,force:true}); require('fs').mkdirSync('public/descargas',{recursive:true})\"";
|
|
434
|
+
scripts["clean:cache"] =
|
|
435
|
+
"node -e \"require('fs').rmSync('.slidev',{recursive:true,force:true}); require('fs').rmSync('node_modules/.vite',{recursive:true,force:true})\"";
|
|
436
|
+
|
|
231
437
|
scripts["build:index"] = "slidev build slides.md --out dist --base / --without-notes";
|
|
232
438
|
|
|
233
439
|
for (let i = 1; i <= weeksTotal; i++) {
|
|
234
440
|
const slug = `${courseShort}_semana${i}`;
|
|
235
|
-
scripts[`build:s${i}`] =
|
|
441
|
+
scripts[`build:s${i}`] =
|
|
442
|
+
`slidev build ${slug}.md --out dist/semanas/${slug} --base /semanas/${slug}/ --without-notes`;
|
|
236
443
|
}
|
|
237
444
|
|
|
238
445
|
scripts["build:all"] = "node scripts/build-site.mjs";
|
|
239
446
|
scripts["build:incremental"] = "node scripts/build-incremental.mjs";
|
|
447
|
+
|
|
240
448
|
scripts["export:downloads"] = "node scripts/export-downloads.mjs";
|
|
241
449
|
scripts["export:incremental"] = "node scripts/export-incremental.mjs";
|
|
242
|
-
|
|
450
|
+
|
|
451
|
+
scripts.vista =
|
|
452
|
+
"npm run config && npm run clean && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1 -o";
|
|
453
|
+
|
|
243
454
|
scripts.publicar = "node scripts/publicar.mjs";
|
|
244
455
|
scripts["dev:todo"] = "node scripts/dev-all.mjs";
|
|
245
456
|
scripts.nuevo = "node scripts/nuevo-curso.mjs";
|
|
246
457
|
scripts["zip:template"] = "node scripts/zip-template.mjs";
|
|
247
|
-
|
|
248
|
-
scripts["preview:
|
|
249
|
-
scripts["pages
|
|
458
|
+
|
|
459
|
+
scripts["preview:static"] = "npm run config && npm run build:all && http-server dist -p 4173 -c-1";
|
|
460
|
+
scripts["preview:pages"] =
|
|
461
|
+
"npm run config && npm run export:downloads && npm run build:all && http-server dist -p 4173 -c-1";
|
|
462
|
+
|
|
463
|
+
scripts["pages:build"] = "npm run config && npm run export:downloads && npm run build:all";
|
|
250
464
|
scripts["pages:preview"] = "npm run preview:pages";
|
|
251
465
|
|
|
252
466
|
return scripts;
|
|
@@ -254,10 +468,21 @@ function buildScripts() {
|
|
|
254
468
|
|
|
255
469
|
function ensureProjectAssets() {
|
|
256
470
|
ensureDir("public");
|
|
471
|
+
ensureDir("public/fondos");
|
|
257
472
|
ensureDir("public/descargas");
|
|
258
473
|
ensureDir("public/imagenes");
|
|
259
474
|
ensureDir("public/videos");
|
|
260
475
|
|
|
476
|
+
writeFileSafe("public/descargas/.gitkeep", "", {
|
|
477
|
+
overwrite: false,
|
|
478
|
+
label: "public/descargas/.gitkeep",
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
writeFileSafe("public/videos/.gitkeep", "", {
|
|
482
|
+
overwrite: false,
|
|
483
|
+
label: "public/videos/.gitkeep",
|
|
484
|
+
});
|
|
485
|
+
|
|
261
486
|
const rootFavicon = "public/favicon.png";
|
|
262
487
|
const imageFavicon = "public/imagenes/favicon.png";
|
|
263
488
|
|
|
@@ -272,20 +497,39 @@ function ensureProjectAssets() {
|
|
|
272
497
|
}
|
|
273
498
|
|
|
274
499
|
if (!fs.existsSync(rootFavicon) && !fs.existsSync(imageFavicon)) {
|
|
275
|
-
writeBinaryFileIfMissing(rootFavicon, FALLBACK_FAVICON_PNG_BASE64, {
|
|
276
|
-
|
|
500
|
+
writeBinaryFileIfMissing(rootFavicon, FALLBACK_FAVICON_PNG_BASE64, {
|
|
501
|
+
label: rootFavicon,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
writeBinaryFileIfMissing(imageFavicon, FALLBACK_FAVICON_PNG_BASE64, {
|
|
505
|
+
label: imageFavicon,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const missingAssets = REQUIRED_THEME_ASSETS.filter((asset) => !fs.existsSync(asset));
|
|
510
|
+
|
|
511
|
+
if (missingAssets.length) {
|
|
512
|
+
console.log("\n⚠️ Recursos visuales obligatorios faltantes:");
|
|
513
|
+
for (const asset of missingAssets) {
|
|
514
|
+
console.log(` - ${asset}`);
|
|
515
|
+
}
|
|
516
|
+
console.log(" El proyecto puede generarse, pero algunas diapositivas podrían verse incompletas.\n");
|
|
277
517
|
}
|
|
278
518
|
}
|
|
279
519
|
|
|
280
520
|
function cleanLegacyDemoFiles() {
|
|
281
521
|
const shouldClean = forceCleanDemo || courseShort !== "demo";
|
|
522
|
+
|
|
282
523
|
if (!shouldClean) {
|
|
283
524
|
console.log("⏭ Archivos demo conservados.");
|
|
284
525
|
return;
|
|
285
526
|
}
|
|
286
527
|
|
|
287
528
|
for (let i = 1; i <= 8; i += 1) {
|
|
288
|
-
removeFileIfExists(`demo_semana${i}.md`, {
|
|
529
|
+
removeFileIfExists(`demo_semana${i}.md`, {
|
|
530
|
+
label: `demo_semana${i}.md`,
|
|
531
|
+
});
|
|
532
|
+
|
|
289
533
|
removeFileIfExists(path.join("semanas", `demo_semana${i}.md`), {
|
|
290
534
|
label: path.join("semanas", `demo_semana${i}.md`),
|
|
291
535
|
});
|
|
@@ -299,22 +543,31 @@ console.log(`Configuración : ${configPath}`);
|
|
|
299
543
|
console.log(`Curso : ${courseName}`);
|
|
300
544
|
console.log(`Nombre corto : ${courseShort}`);
|
|
301
545
|
console.log(`Semanas : ${weeksTotal}`);
|
|
302
|
-
console.log(`Activas : ${activeWeeks.map((
|
|
546
|
+
console.log(`Activas : ${activeWeeks.map((week) => week.number).join(", ") || "ninguna"}\n`);
|
|
303
547
|
|
|
304
548
|
ensureDir("semanas");
|
|
305
549
|
ensureProjectAssets();
|
|
306
550
|
cleanLegacyDemoFiles();
|
|
307
551
|
|
|
308
|
-
writeFileSafe("slides.md", buildPortal(), {
|
|
309
|
-
|
|
552
|
+
writeFileSafe("slides.md", buildPortal(), {
|
|
553
|
+
overwrite: overwritePortal,
|
|
554
|
+
label: "slides.md",
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
writeFileSafe("scripts/decks.mjs", buildDecks(), {
|
|
558
|
+
overwrite: overwriteDecks,
|
|
559
|
+
label: "scripts/decks.mjs",
|
|
560
|
+
});
|
|
310
561
|
|
|
311
562
|
for (const week of allWeeks) {
|
|
312
563
|
const slug = `${courseShort}_semana${week.number}`;
|
|
313
564
|
const tokens = weekTokens(week);
|
|
565
|
+
|
|
314
566
|
writeFileSafe(`${slug}.md`, replaceTokens(launcherTemplate, tokens), {
|
|
315
567
|
overwrite: overwriteLaunchers,
|
|
316
568
|
label: `${slug}.md`,
|
|
317
569
|
});
|
|
570
|
+
|
|
318
571
|
writeFileSafe(path.join("semanas", `${slug}.md`), replaceTokens(semanaTemplate, tokens), {
|
|
319
572
|
overwrite: overwriteWeekContent,
|
|
320
573
|
label: path.join("semanas", `${slug}.md`),
|
|
@@ -323,20 +576,31 @@ for (const week of allWeeks) {
|
|
|
323
576
|
|
|
324
577
|
if (overwritePackageScripts && fs.existsSync("package.json")) {
|
|
325
578
|
const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
579
|
+
|
|
326
580
|
pkg.name = `openclass-${courseShort}`;
|
|
327
581
|
pkg.description = `Presentaciones Open Class del curso ${courseName}.`;
|
|
328
582
|
pkg.scripts = buildScripts();
|
|
329
|
-
|
|
583
|
+
|
|
584
|
+
writeFileSafe("package.json", JSON.stringify(pkg, null, 2) + "\n", {
|
|
585
|
+
overwrite: true,
|
|
586
|
+
label: "package.json",
|
|
587
|
+
});
|
|
330
588
|
}
|
|
331
589
|
|
|
332
590
|
if (fs.existsSync("package-lock.json")) {
|
|
333
591
|
try {
|
|
334
592
|
const lock = JSON.parse(fs.readFileSync("package-lock.json", "utf-8"));
|
|
593
|
+
|
|
335
594
|
lock.name = `openclass-${courseShort}`;
|
|
595
|
+
|
|
336
596
|
if (lock.packages && lock.packages[""]) {
|
|
337
597
|
lock.packages[""].name = `openclass-${courseShort}`;
|
|
338
598
|
}
|
|
339
|
-
|
|
599
|
+
|
|
600
|
+
writeFileSafe("package-lock.json", JSON.stringify(lock, null, 2) + "\n", {
|
|
601
|
+
overwrite: true,
|
|
602
|
+
label: "package-lock.json",
|
|
603
|
+
});
|
|
340
604
|
} catch {
|
|
341
605
|
console.log("⚠️ package-lock.json no se actualizó porque no parece ser JSON válido.");
|
|
342
606
|
}
|
|
@@ -345,4 +609,4 @@ if (fs.existsSync("package-lock.json")) {
|
|
|
345
609
|
console.log("\n✅ Configuración generada.");
|
|
346
610
|
console.log(" Próximo paso sugerido: npm install");
|
|
347
611
|
console.log(" Vista de una semana: npm run dev:s1");
|
|
348
|
-
console.log(" Vista completa: npm run vista\n");
|
|
612
|
+
console.log(" Vista completa: npm run vista\n");
|
package/template/slides.md
CHANGED
|
@@ -58,7 +58,7 @@ Presentaciones disponibles
|
|
|
58
58
|
::right::
|
|
59
59
|
### Próximamente
|
|
60
60
|
|
|
61
|
-
Activa más semanas
|
|
61
|
+
Activa más semanas con `npm run semana -- 2`, `npm run semana -- 3` y así sucesivamente.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
layout: slide-12-cierre
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
overflow: hidden;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
.uni-stack-slot
|
|
96
|
-
.uni-image-slot
|
|
97
|
-
.uni-media-slot
|
|
95
|
+
.uni-stack-slot p,
|
|
96
|
+
.uni-image-slot p,
|
|
97
|
+
.uni-media-slot p {
|
|
98
98
|
margin: 0;
|
|
99
99
|
width: 100%;
|
|
100
100
|
max-width: 100%;
|
|
@@ -105,8 +105,8 @@
|
|
|
105
105
|
gap: 0.55rem;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
.uni-stack-slot
|
|
109
|
-
.uni-image-slot
|
|
108
|
+
.uni-stack-slot img,
|
|
109
|
+
.uni-image-slot img {
|
|
110
110
|
display: block;
|
|
111
111
|
width: auto;
|
|
112
112
|
height: auto;
|
|
@@ -116,9 +116,9 @@
|
|
|
116
116
|
object-position: center;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
.uni-media-slot
|
|
120
|
-
.uni-media-slot
|
|
121
|
-
.uni-media-slot
|
|
119
|
+
.uni-media-slot img,
|
|
120
|
+
.uni-media-slot video,
|
|
121
|
+
.uni-media-slot iframe {
|
|
122
122
|
display: block;
|
|
123
123
|
width: 100%;
|
|
124
124
|
height: 100%;
|
|
@@ -212,3 +212,4 @@
|
|
|
212
212
|
background: rgba(242, 181, 27, 0.8);
|
|
213
213
|
border-radius: 999px;
|
|
214
214
|
}
|
|
215
|
+
|