@learnpack/learnpack 5.0.288 → 5.0.290
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/lib/commands/serve.js +49 -73
- package/lib/creatorDist/assets/{index-BjO3hUuz.js → index-7zTdUX04.js} +4471 -4492
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/export/index.d.ts +1 -0
- package/lib/utils/export/index.js +3 -1
- package/lib/utils/export/types.d.ts +1 -1
- package/lib/utils/export/zip.d.ts +2 -0
- package/lib/utils/export/zip.js +46 -0
- package/package.json +1 -1
- package/src/commands/serve.ts +28 -54
- package/src/creator/src/App.tsx +61 -44
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +1 -21
- package/src/creator/src/utils/lib.ts +468 -477
- package/src/creator/src/utils/store.ts +222 -223
- package/src/creatorDist/assets/{index-BjO3hUuz.js → index-7zTdUX04.js} +4471 -4492
- package/src/creatorDist/index.html +1 -1
- package/src/utils/export/index.ts +1 -0
- package/src/utils/export/types.ts +1 -1
- package/src/utils/export/zip.ts +55 -0
- package/src/creator/src/components/PassphraseValidator.tsx +0 -47
@@ -10,7 +10,7 @@
|
|
10
10
|
/>
|
11
11
|
|
12
12
|
<title>Learnpack Creator: Craft tutorials in seconds!</title>
|
13
|
-
<script type="module" crossorigin src="/creator/assets/index-
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-7zTdUX04.js"></script>
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-C39zeF3W.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
@@ -1,7 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.exportToEpub = exports.exportToScorm = void 0;
|
3
|
+
exports.exportToZip = exports.exportToEpub = exports.exportToScorm = void 0;
|
4
4
|
var scorm_1 = require("./scorm");
|
5
5
|
Object.defineProperty(exports, "exportToScorm", { enumerable: true, get: function () { return scorm_1.exportToScorm; } });
|
6
6
|
var epub_1 = require("./epub");
|
7
7
|
Object.defineProperty(exports, "exportToEpub", { enumerable: true, get: function () { return epub_1.exportToEpub; } });
|
8
|
+
var zip_1 = require("./zip");
|
9
|
+
Object.defineProperty(exports, "exportToZip", { enumerable: true, get: function () { return zip_1.exportToZip; } });
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.exportToZip = exportToZip;
|
4
|
+
const path = require("path");
|
5
|
+
const fs = require("fs");
|
6
|
+
const archiver = require("archiver");
|
7
|
+
const mkdirp = require("mkdirp");
|
8
|
+
const rimraf = require("rimraf");
|
9
|
+
const uuid_1 = require("uuid");
|
10
|
+
const shared_1 = require("./shared");
|
11
|
+
async function exportToZip(options) {
|
12
|
+
const { courseSlug, bucket, outDir } = options;
|
13
|
+
// 1. Create temporary folder
|
14
|
+
const tmpName = (0, uuid_1.v4)();
|
15
|
+
const zipOutDir = path.join(outDir, tmpName);
|
16
|
+
rimraf.sync(zipOutDir);
|
17
|
+
mkdirp.sync(zipOutDir);
|
18
|
+
// 2. Download all course files from bucket to temporary directory
|
19
|
+
await (0, shared_1.downloadS3Folder)(bucket, `courses/${courseSlug}/`, zipOutDir);
|
20
|
+
// 3. Create ZIP file
|
21
|
+
const zipName = `${courseSlug}.zip`;
|
22
|
+
const zipPath = path.join(outDir, zipName);
|
23
|
+
// Remove existing zip file if it exists
|
24
|
+
if (fs.existsSync(zipPath)) {
|
25
|
+
fs.unlinkSync(zipPath);
|
26
|
+
}
|
27
|
+
const output = fs.createWriteStream(zipPath);
|
28
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
29
|
+
return new Promise((resolve, reject) => {
|
30
|
+
output.on("close", () => {
|
31
|
+
console.log(`✅ ZIP export completed: ${archive.pointer()} total bytes`);
|
32
|
+
// Clean up temporary directory
|
33
|
+
rimraf.sync(zipOutDir);
|
34
|
+
resolve(zipPath);
|
35
|
+
});
|
36
|
+
archive.on("error", (err) => {
|
37
|
+
console.error("❌ ZIP creation error:", err);
|
38
|
+
rimraf.sync(zipOutDir);
|
39
|
+
reject(err);
|
40
|
+
});
|
41
|
+
archive.pipe(output);
|
42
|
+
// Add all files from the temporary directory to the ZIP
|
43
|
+
archive.directory(zipOutDir, false);
|
44
|
+
archive.finalize();
|
45
|
+
});
|
46
|
+
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
|
4
|
-
"version": "5.0.
|
4
|
+
"version": "5.0.290",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/serve.ts
CHANGED
@@ -56,7 +56,7 @@ import { checkReadability, slugify } from "../utils/creatorUtilities"
|
|
56
56
|
import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
|
57
57
|
import { handleAssetCreation } from "./publish"
|
58
58
|
import { FormState, Lesson, Syllabus } from "../models/creator"
|
59
|
-
import { exportToScorm, exportToEpub } from "../utils/export"
|
59
|
+
import { exportToScorm, exportToEpub, exportToZip } from "../utils/export"
|
60
60
|
|
61
61
|
const frontMatter = require("front-matter")
|
62
62
|
|
@@ -698,6 +698,7 @@ export default class ServeCommand extends SessionCommand {
|
|
698
698
|
}
|
699
699
|
)
|
700
700
|
|
701
|
+
// TODO: Check if this command is being used
|
701
702
|
app.post("/actions/generate-image/:courseSlug", async (req, res) => {
|
702
703
|
const rigoToken = req.header("x-rigo-token")
|
703
704
|
const { courseSlug } = req.params
|
@@ -1039,8 +1040,6 @@ export default class ServeCommand extends SessionCommand {
|
|
1039
1040
|
const courseSlug = req.query.slug
|
1040
1041
|
const lang = req.query.lang || "en"
|
1041
1042
|
|
1042
|
-
console.log("LANG", lang)
|
1043
|
-
|
1044
1043
|
if (!courseSlug) {
|
1045
1044
|
return res.status(400).json({ error: "Missing courseSlug" })
|
1046
1045
|
}
|
@@ -1574,53 +1573,6 @@ export default class ServeCommand extends SessionCommand {
|
|
1574
1573
|
}
|
1575
1574
|
})
|
1576
1575
|
|
1577
|
-
// app.post("/actions/continue-course/:courseSlug", async (req, res) => {
|
1578
|
-
// console.log("POST /actions/continue-course/:courseSlug")
|
1579
|
-
// const { courseSlug } = req.params
|
1580
|
-
|
1581
|
-
// const { feedback }: { feedback: string } = req.body
|
1582
|
-
|
1583
|
-
// const rigoToken = req.header("x-rigo-token")
|
1584
|
-
// const bcToken = req.header("x-breathecode-token")
|
1585
|
-
// if (!rigoToken || !bcToken) {
|
1586
|
-
// return res.status(400).json({ error: "Missing tokens" })
|
1587
|
-
// }
|
1588
|
-
|
1589
|
-
// const syllabus = await bucket.file(
|
1590
|
-
// `courses/${courseSlug}/.learn/initialSyllabus.json`
|
1591
|
-
// )
|
1592
|
-
// const [content] = await syllabus.download()
|
1593
|
-
// const syllabusJson: Syllabus = JSON.parse(content.toString())
|
1594
|
-
// const notGeneratedLessons = syllabusJson.lessons.filter(
|
1595
|
-
// lesson => !lesson.generated
|
1596
|
-
// )
|
1597
|
-
|
1598
|
-
// const lastGeneratedLesson = findLast(
|
1599
|
-
// syllabusJson.lessons,
|
1600
|
-
// lesson => lesson.generated ?? false
|
1601
|
-
// )
|
1602
|
-
|
1603
|
-
// console.log("ABout to generate", notGeneratedLessons.length, "lessons")
|
1604
|
-
|
1605
|
-
// const firstLessonToGenerate = notGeneratedLessons[0]
|
1606
|
-
|
1607
|
-
// const completionId = await startExerciseGeneration(
|
1608
|
-
// rigoToken,
|
1609
|
-
// syllabusJson.lessons,
|
1610
|
-
// syllabusJson.courseInfo,
|
1611
|
-
// firstLessonToGenerate,
|
1612
|
-
// courseSlug,
|
1613
|
-
// syllabusJson.courseInfo.purpose,
|
1614
|
-
// JSON.stringify(lastGeneratedLesson) +
|
1615
|
-
// `\n\nThe user provided this feedback in relation to the course: ${feedback}`
|
1616
|
-
// )
|
1617
|
-
|
1618
|
-
// return res.json({
|
1619
|
-
// message: "Course continued",
|
1620
|
-
// slug: courseSlug,
|
1621
|
-
// })
|
1622
|
-
// })
|
1623
|
-
|
1624
1576
|
app.get(
|
1625
1577
|
"/courses/:courseSlug/exercises/:exerciseSlug/",
|
1626
1578
|
async (req, res) => {
|
@@ -2048,7 +2000,8 @@ export default class ServeCommand extends SessionCommand {
|
|
2048
2000
|
let outputPath: string
|
2049
2001
|
let filename: string
|
2050
2002
|
|
2051
|
-
|
2003
|
+
switch (format) {
|
2004
|
+
case "scorm": {
|
2052
2005
|
outputPath = await exportToScorm({
|
2053
2006
|
courseSlug: course_slug,
|
2054
2007
|
format: "scorm",
|
@@ -2056,7 +2009,23 @@ export default class ServeCommand extends SessionCommand {
|
|
2056
2009
|
outDir: path.join(__dirname, "../output/directory"),
|
2057
2010
|
})
|
2058
2011
|
filename = `${course_slug}-scorm.zip`
|
2059
|
-
|
2012
|
+
|
2013
|
+
break
|
2014
|
+
}
|
2015
|
+
|
2016
|
+
case "zip": {
|
2017
|
+
outputPath = await exportToZip({
|
2018
|
+
courseSlug: course_slug,
|
2019
|
+
format: "zip",
|
2020
|
+
bucket,
|
2021
|
+
outDir: path.join(__dirname, "../output/directory"),
|
2022
|
+
})
|
2023
|
+
filename = `${course_slug}.zip`
|
2024
|
+
|
2025
|
+
break
|
2026
|
+
}
|
2027
|
+
|
2028
|
+
case "epub": {
|
2060
2029
|
console.log("EPUB export", metadata)
|
2061
2030
|
// Validate required metadata for EPUB
|
2062
2031
|
if (
|
@@ -2085,11 +2054,16 @@ export default class ServeCommand extends SessionCommand {
|
|
2085
2054
|
metadata
|
2086
2055
|
)
|
2087
2056
|
filename = `${course_slug}.epub`
|
2088
|
-
|
2057
|
+
|
2058
|
+
break
|
2059
|
+
}
|
2060
|
+
|
2061
|
+
default: {
|
2089
2062
|
return res.status(400).json({
|
2090
|
-
error: "Invalid format. Supported formats: scorm, epub",
|
2063
|
+
error: "Invalid format. Supported formats: scorm, epub, zip",
|
2091
2064
|
})
|
2092
2065
|
}
|
2066
|
+
}
|
2093
2067
|
|
2094
2068
|
// Send the file and clean up
|
2095
2069
|
res.download(outputPath, filename, err => {
|
package/src/creator/src/App.tsx
CHANGED
@@ -6,7 +6,7 @@ import { useNavigate } from "react-router"
|
|
6
6
|
import { useShallow } from "zustand/react/shallow"
|
7
7
|
import useStore, { TDifficulty } from "./utils/store"
|
8
8
|
|
9
|
-
import { publicInteractiveCreation } from "./utils/rigo"
|
9
|
+
import { publicInteractiveCreation, isHuman } from "./utils/rigo"
|
10
10
|
import {
|
11
11
|
checkParams,
|
12
12
|
isValidRigoToken,
|
@@ -21,8 +21,8 @@ import {
|
|
21
21
|
import { Uploader } from "./components/Uploader"
|
22
22
|
import toast from "react-hot-toast"
|
23
23
|
import { ParamsChecker } from "./components/ParamsChecker"
|
24
|
-
import { RIGO_FLOAT_GIF } from "./utils/constants"
|
25
|
-
|
24
|
+
import { DEV_MODE, RIGO_FLOAT_GIF } from "./utils/constants"
|
25
|
+
import TurnstileChallenge from "./components/TurnstileChallenge"
|
26
26
|
// import TurnstileChallenge from "./components/TurnstileChallenge"
|
27
27
|
import ResumeCourseModal from "./components/ResumeCourseModal"
|
28
28
|
import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
|
@@ -31,7 +31,6 @@ import NotificationListener from "./components/NotificationListener"
|
|
31
31
|
import { slugify } from "./utils/creatorUtils"
|
32
32
|
import TurnstileModal from "./components/TurnstileModal"
|
33
33
|
import { TMessage } from "./components/Message"
|
34
|
-
import PassphraseValidator from "./components/PassphraseValidator"
|
35
34
|
|
36
35
|
function App() {
|
37
36
|
const navigate = useNavigate()
|
@@ -86,22 +85,6 @@ function App() {
|
|
86
85
|
checkTechs()
|
87
86
|
}, [])
|
88
87
|
|
89
|
-
const tokenVerification = async () => {
|
90
|
-
const isValid = await isValidPublicToken(auth.publicToken)
|
91
|
-
if (!isValid) {
|
92
|
-
setAuth({
|
93
|
-
...auth,
|
94
|
-
publicToken: ""
|
95
|
-
})
|
96
|
-
}
|
97
|
-
}
|
98
|
-
|
99
|
-
useEffect(() => {
|
100
|
-
if (auth.publicToken) {
|
101
|
-
tokenVerification()
|
102
|
-
}
|
103
|
-
}, [auth])
|
104
|
-
|
105
88
|
const verifyToken = async () => {
|
106
89
|
const { token } = checkParams(["token"])
|
107
90
|
if (token) {
|
@@ -140,6 +123,7 @@ function App() {
|
|
140
123
|
}
|
141
124
|
|
142
125
|
if (description) {
|
126
|
+
console.log("description", description)
|
143
127
|
setFormState({
|
144
128
|
description: description,
|
145
129
|
})
|
@@ -147,9 +131,12 @@ function App() {
|
|
147
131
|
if (duration && !isNaN(parseInt(duration))) {
|
148
132
|
if (["30", "60", "120"].includes(duration)) {
|
149
133
|
const durationInt = parseInt(duration)
|
134
|
+
console.log("duration", durationInt)
|
150
135
|
setFormState({
|
151
136
|
duration: durationInt,
|
152
137
|
})
|
138
|
+
} else {
|
139
|
+
console.log("Invalid duration received in params", duration)
|
153
140
|
}
|
154
141
|
}
|
155
142
|
|
@@ -215,13 +202,13 @@ function App() {
|
|
215
202
|
}
|
216
203
|
}
|
217
204
|
|
218
|
-
if (auth.publicToken) {
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
205
|
+
if (auth.publicToken && !isAuthenticated) {
|
206
|
+
const isPublicTokenValid = await isValidPublicToken(auth.publicToken)
|
207
|
+
if (isPublicTokenValid) {
|
208
|
+
tokenToUse = auth.publicToken
|
209
|
+
isAuthenticated = true
|
210
|
+
tokenType = "public"
|
211
|
+
}
|
225
212
|
}
|
226
213
|
if (!isAuthenticated) {
|
227
214
|
setShowTurnstileModal(true)
|
@@ -308,7 +295,7 @@ function App() {
|
|
308
295
|
onFinish={(purpose) => {
|
309
296
|
setFormState({
|
310
297
|
purpose: purpose,
|
311
|
-
currentStep: "
|
298
|
+
currentStep: "verifyHuman",
|
312
299
|
})
|
313
300
|
}}
|
314
301
|
/>
|
@@ -327,7 +314,7 @@ function App() {
|
|
327
314
|
onClick={() => {
|
328
315
|
setFormState({
|
329
316
|
duration: 30,
|
330
|
-
currentStep: "
|
317
|
+
currentStep: "verifyHuman",
|
331
318
|
})
|
332
319
|
}}
|
333
320
|
selected={formState.duration === 30}
|
@@ -338,7 +325,7 @@ function App() {
|
|
338
325
|
onClick={() => {
|
339
326
|
setFormState({
|
340
327
|
duration: 60,
|
341
|
-
currentStep: "
|
328
|
+
currentStep: "verifyHuman",
|
342
329
|
})
|
343
330
|
}}
|
344
331
|
selected={formState.duration === 60}
|
@@ -349,7 +336,7 @@ function App() {
|
|
349
336
|
onClick={() => {
|
350
337
|
setFormState({
|
351
338
|
duration: 120,
|
352
|
-
currentStep: "
|
339
|
+
currentStep: "verifyHuman",
|
353
340
|
})
|
354
341
|
}}
|
355
342
|
selected={formState.duration === 120}
|
@@ -358,20 +345,52 @@ function App() {
|
|
358
345
|
),
|
359
346
|
},
|
360
347
|
{
|
361
|
-
title: t("stepWizard.
|
362
|
-
slug: "
|
348
|
+
title: t("stepWizard.verifyHuman"),
|
349
|
+
slug: "verifyHuman",
|
363
350
|
isCompleted: false,
|
351
|
+
required: true,
|
364
352
|
content: (
|
365
|
-
|
366
|
-
{
|
367
|
-
|
368
|
-
|
353
|
+
<TurnstileChallenge
|
354
|
+
siteKey={
|
355
|
+
DEV_MODE ? "0x4AAAAAABeKMBYYinMU4Ib0" : "0x4AAAAAABeZ9tjEevGBsJFU"
|
356
|
+
}
|
357
|
+
onSuccess={async (token) => {
|
358
|
+
const { human, message, token: jwtToken } = await isHuman(token)
|
359
|
+
if (human) {
|
360
|
+
toast.success(t("stepWizard.humanSuccess"))
|
361
|
+
|
362
|
+
console.log("JWT TOKEN received", jwtToken)
|
369
363
|
setAuth({
|
370
364
|
...auth,
|
371
|
-
publicToken:
|
365
|
+
publicToken: jwtToken,
|
366
|
+
})
|
367
|
+
setFormState({
|
368
|
+
currentStep: "hasContentIndex",
|
372
369
|
})
|
373
|
-
}
|
374
|
-
|
370
|
+
} else {
|
371
|
+
toast.error(message)
|
372
|
+
setFormState({
|
373
|
+
currentStep: "duration",
|
374
|
+
})
|
375
|
+
}
|
376
|
+
}}
|
377
|
+
onError={() => {
|
378
|
+
toast.error(t("turnstileModal.error"), {
|
379
|
+
duration: 10000,
|
380
|
+
})
|
381
|
+
setFormState({
|
382
|
+
currentStep: "duration",
|
383
|
+
})
|
384
|
+
}}
|
385
|
+
/>
|
386
|
+
),
|
387
|
+
},
|
388
|
+
{
|
389
|
+
title: t("stepWizard.hasContentIndex"),
|
390
|
+
slug: "hasContentIndex",
|
391
|
+
isCompleted: false,
|
392
|
+
content: (
|
393
|
+
<>
|
375
394
|
<div className="flex flex-col md:flex-row gap-2 justify-center">
|
376
395
|
<SelectableCard
|
377
396
|
title={t("stepWizard.hasContentIndexCard.no")}
|
@@ -386,7 +405,7 @@ function App() {
|
|
386
405
|
isCompleted: true,
|
387
406
|
})
|
388
407
|
}}
|
389
|
-
|
408
|
+
// selected={formState.hasContentIndex === false}
|
390
409
|
/>
|
391
410
|
<SelectableCard
|
392
411
|
title={t("stepWizard.hasContentIndexCard.yes")}
|
@@ -397,7 +416,7 @@ function App() {
|
|
397
416
|
variables: [...formState.variables, "contentIndex"],
|
398
417
|
})
|
399
418
|
}}
|
400
|
-
|
419
|
+
// selected={formState.hasContentIndex === true}
|
401
420
|
/>
|
402
421
|
</div>
|
403
422
|
</>
|
@@ -466,11 +485,9 @@ function App() {
|
|
466
485
|
|
467
486
|
push({
|
468
487
|
lessons,
|
469
|
-
generationMode: "continue-with-all",
|
470
488
|
courseInfo: {
|
471
489
|
...formState,
|
472
490
|
title: res.parsed.title,
|
473
|
-
|
474
491
|
slug: slugify(fixTitleLength(res.parsed.title)),
|
475
492
|
description: res.parsed.description,
|
476
493
|
language:
|
@@ -14,7 +14,6 @@ import {
|
|
14
14
|
useConsumableCall,
|
15
15
|
isValidRigoToken,
|
16
16
|
isValidPublicToken,
|
17
|
-
getMyPackages,
|
18
17
|
} from "../../utils/lib"
|
19
18
|
|
20
19
|
import Loader from "../Loader"
|
@@ -75,7 +74,7 @@ const SyllabusEditor: React.FC = () => {
|
|
75
74
|
}, [syllabus, navigate])
|
76
75
|
|
77
76
|
useEffect(() => {
|
78
|
-
;
|
77
|
+
;(async () => {
|
79
78
|
const { token } = checkParams(["token"])
|
80
79
|
if (token) {
|
81
80
|
const user = await loginWithToken(token)
|
@@ -93,25 +92,6 @@ const SyllabusEditor: React.FC = () => {
|
|
93
92
|
checkSlug()
|
94
93
|
}, [])
|
95
94
|
|
96
|
-
|
97
|
-
const checkMyPackages = async () => {
|
98
|
-
if (auth.rigoToken) {
|
99
|
-
const packages = await getMyPackages(auth.rigoToken)
|
100
|
-
if (Array.isArray(packages) && packages.length === 0) {
|
101
|
-
push({
|
102
|
-
...syllabus,
|
103
|
-
generationMode: "continue-with-all",
|
104
|
-
})
|
105
|
-
}
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
useEffect(() => {
|
110
|
-
if (auth.rigoToken) {
|
111
|
-
checkMyPackages()
|
112
|
-
}
|
113
|
-
}, [auth])
|
114
|
-
|
115
95
|
const checkSlug = async () => {
|
116
96
|
if (!syllabus.courseInfo.title) {
|
117
97
|
toast.error("Please provide a title for the course")
|