@learnpack/learnpack 5.0.96 → 5.0.100
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/README.md +13 -13
- package/lib/commands/serve.js +62 -50
- package/lib/creatorDist/assets/{index-ldEC0yWM.css → index-fdwyhdDD.css} +27 -0
- package/lib/creatorDist/assets/{index-DayC-cyC.js → index-mdqn3dGF.js} +44774 -37111
- package/lib/creatorDist/index.html +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +109 -83
- package/src/creator/package-lock.json +50 -0
- package/src/creator/package.json +1 -0
- package/src/creator/src/components/PreviewGenerator.tsx +95 -0
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +14 -6
- package/src/creator/src/main.tsx +2 -0
- package/src/creator/src/utils/creatorUtils.ts +1 -0
- package/src/creatorDist/assets/{index-ldEC0yWM.css → index-fdwyhdDD.css} +27 -0
- package/src/creatorDist/assets/{index-DayC-cyC.js → index-mdqn3dGF.js} +44774 -37111
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +278 -278
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/convertCreds.js +4 -0
@@ -10,8 +10,8 @@
|
|
10
10
|
/>
|
11
11
|
|
12
12
|
<title>Learnpack Creator: Craft tutorials in seconds!</title>
|
13
|
-
<script type="module" crossorigin src="/creator/assets/index-
|
14
|
-
<link rel="stylesheet" crossorigin href="/creator/assets/index-
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-mdqn3dGF.js"></script>
|
14
|
+
<link rel="stylesheet" crossorigin href="/creator/assets/index-fdwyhdDD.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
17
17
|
<div id="root"></div>
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.100","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|
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.100",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/serve.ts
CHANGED
@@ -10,7 +10,7 @@ import SessionCommand from "../utils/SessionCommand"
|
|
10
10
|
import { Storage } from "@google-cloud/storage"
|
11
11
|
import { downloadEditor, decompress } from "../managers/file"
|
12
12
|
import * as fs from "fs"
|
13
|
-
import { translateExercise } from "../utils/rigoActions"
|
13
|
+
import { translateExercise, isValidRigoToken } from "../utils/rigoActions"
|
14
14
|
import * as dotenv from "dotenv"
|
15
15
|
// import { handleAssetCreation } from "./publish"
|
16
16
|
import axios from "axios"
|
@@ -23,6 +23,20 @@ dotenv.config()
|
|
23
23
|
|
24
24
|
const frontMatter = require("front-matter")
|
25
25
|
|
26
|
+
const fixPreviewUrl = (slug: string, previewUrl: string) => {
|
27
|
+
if (!previewUrl) {
|
28
|
+
return null
|
29
|
+
}
|
30
|
+
|
31
|
+
if (previewUrl.startsWith("http")) {
|
32
|
+
return previewUrl
|
33
|
+
}
|
34
|
+
|
35
|
+
const expectedUrl = `https://${slug}.learn-pack.com/preview.png`
|
36
|
+
console.log("EXPECTED URL", expectedUrl)
|
37
|
+
return expectedUrl
|
38
|
+
}
|
39
|
+
|
26
40
|
export default class ServeCommand extends SessionCommand {
|
27
41
|
static description = "Runs a small server to build tutorials"
|
28
42
|
|
@@ -126,39 +140,43 @@ export default class ServeCommand extends SessionCommand {
|
|
126
140
|
stream.end(buffer)
|
127
141
|
})
|
128
142
|
|
129
|
-
app.post(
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
}
|
136
|
-
|
137
|
-
try {
|
138
|
-
const response = await fetch(image_url)
|
139
|
-
if (!response.ok) {
|
143
|
+
app.post(
|
144
|
+
"/upload-image",
|
145
|
+
express.json({ limit: "20mb" }),
|
146
|
+
async (req, res) => {
|
147
|
+
const { image_url, destination } = req.body
|
148
|
+
if (!image_url || !destination) {
|
140
149
|
return res
|
141
150
|
.status(400)
|
142
|
-
.json({ error:
|
151
|
+
.json({ error: "image_url and destination are required" })
|
143
152
|
}
|
144
153
|
|
145
|
-
|
146
|
-
response
|
147
|
-
|
154
|
+
try {
|
155
|
+
const response = await fetch(image_url)
|
156
|
+
if (!response.ok) {
|
157
|
+
return res.status(400).json({
|
158
|
+
error: `Failed to download image: ${response.statusText}`,
|
159
|
+
})
|
160
|
+
}
|
148
161
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
contentType,
|
153
|
-
})
|
162
|
+
const contentType =
|
163
|
+
response.headers.get("content-type") || "application/octet-stream"
|
164
|
+
const buffer = await response.arrayBuffer()
|
154
165
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
166
|
+
const file = bucket.file(destination)
|
167
|
+
await file.save(Buffer.from(buffer), {
|
168
|
+
resumable: false,
|
169
|
+
contentType,
|
170
|
+
})
|
171
|
+
|
172
|
+
console.log(`✅ Image uploaded to ${file.name}`)
|
173
|
+
res.json({ message: "Image uploaded successfully", path: file.name })
|
174
|
+
} catch (error) {
|
175
|
+
console.error("❌ upload-image error:", error)
|
176
|
+
res.status(500).json({ error: (error as Error).message })
|
177
|
+
}
|
160
178
|
}
|
161
|
-
|
179
|
+
)
|
162
180
|
|
163
181
|
app.get("/create", (req, res) => {
|
164
182
|
console.log("GET /create")
|
@@ -202,59 +220,6 @@ export default class ServeCommand extends SessionCommand {
|
|
202
220
|
}
|
203
221
|
})
|
204
222
|
|
205
|
-
// app.get("/config", async (req, res) => {
|
206
|
-
// const courseSlug = req.query.slug
|
207
|
-
// const files = await listFilesWithPrefix(`courses/${courseSlug}`)
|
208
|
-
|
209
|
-
// const learnJson = files.find((file) => file.name.endsWith("learn.json"))
|
210
|
-
// const learnJsonContent = await learnJson?.download()
|
211
|
-
// const learnJsonParsed = JSON.parse(learnJsonContent?.toString() || "{}")
|
212
|
-
|
213
|
-
// const exerciseMap: ExerciseMap = {}
|
214
|
-
|
215
|
-
// // Agrupar archivos por ejercicio
|
216
|
-
// for (const file of files) {
|
217
|
-
// const pathParts = file.name.split("/")
|
218
|
-
// const isExercise = pathParts.includes("exercises")
|
219
|
-
// if (!isExercise) continue
|
220
|
-
|
221
|
-
// const slug = pathParts[pathParts.indexOf("exercises") + 1]
|
222
|
-
// if (!exerciseMap[slug]) {
|
223
|
-
// exerciseMap[slug] = {
|
224
|
-
// title: slug,
|
225
|
-
// slug: slug,
|
226
|
-
// graded: false,
|
227
|
-
// files: [],
|
228
|
-
// translations: {},
|
229
|
-
// }
|
230
|
-
// }
|
231
|
-
|
232
|
-
// const fileName = pathParts.at(-1)
|
233
|
-
|
234
|
-
// // Traducciones
|
235
|
-
// const readmeMatch = fileName?.match(/^readme(?:\.([a-z]{2}))?\.md$/i)
|
236
|
-
// if (readmeMatch) {
|
237
|
-
// const lang = readmeMatch[1] || "us"
|
238
|
-
// exerciseMap[slug].translations[lang] = fileName || ""
|
239
|
-
// } else {
|
240
|
-
// exerciseMap[slug].files.push(fileName || "")
|
241
|
-
// }
|
242
|
-
// }
|
243
|
-
|
244
|
-
// const exercises = Object.values(exerciseMap).map((ex, index) => ({
|
245
|
-
// ...ex,
|
246
|
-
// position: index,
|
247
|
-
// }))
|
248
|
-
|
249
|
-
// res.set("X-Creator-Web", "true")
|
250
|
-
// res.set("Access-Control-Expose-Headers", "X-Creator-Web")
|
251
|
-
|
252
|
-
// res.send({
|
253
|
-
// config: { ...learnJsonParsed, title: { us: courseSlug } },
|
254
|
-
// exercises,
|
255
|
-
// })
|
256
|
-
// })
|
257
|
-
|
258
223
|
app.get("/exercise/:slug/readme", async (req, res) => {
|
259
224
|
console.log("GET /exercise/:slug/readme")
|
260
225
|
|
@@ -456,7 +421,6 @@ export default class ServeCommand extends SessionCommand {
|
|
456
421
|
|
457
422
|
app.post("/actions/publish/:slug", async (req, res) => {
|
458
423
|
try {
|
459
|
-
// 1) Extraer token y body
|
460
424
|
const { slug } = req.params
|
461
425
|
const rigoToken = req.header("x-rigo-token")
|
462
426
|
const bcToken = req.header("x-breathecode-token")
|
@@ -506,14 +470,13 @@ export default class ServeCommand extends SessionCommand {
|
|
506
470
|
.replace(/{{description}}/g, config.description.us)
|
507
471
|
.replace(
|
508
472
|
/{{preview}}/g,
|
509
|
-
config.preview ||
|
473
|
+
fixPreviewUrl(slug, config.preview) ||
|
510
474
|
"https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg"
|
511
475
|
)
|
512
476
|
.replace(/{{slug}}/g, slug)
|
513
477
|
.replace(/{{duration}}/g, minutesToISO8601Duration(config.duration))
|
514
478
|
fs.writeFileSync(path.join(buildRoot, "index.html"), idxHtml)
|
515
479
|
|
516
|
-
// 6) Inyectar placeholders en manifest.webmanifest
|
517
480
|
const mfTpl = fs.readFileSync(
|
518
481
|
path.join(uiSrc, "manifest.webmanifest"),
|
519
482
|
"utf-8"
|
@@ -594,6 +557,69 @@ export default class ServeCommand extends SessionCommand {
|
|
594
557
|
return res.status(500).json({ error: (error as Error).message })
|
595
558
|
}
|
596
559
|
})
|
560
|
+
|
561
|
+
app.delete("/packages/:slug", async (req, res) => {
|
562
|
+
console.log("DELETE /packages/:slug")
|
563
|
+
|
564
|
+
const { slug } = req.params
|
565
|
+
const rigoToken = req.header("x-rigo-token")
|
566
|
+
|
567
|
+
if (!rigoToken) {
|
568
|
+
return res.status(400).json({ error: "x-rigo-token is required" })
|
569
|
+
}
|
570
|
+
|
571
|
+
const isValid = await isValidRigoToken(rigoToken)
|
572
|
+
|
573
|
+
if (!isValid) {
|
574
|
+
return res.status(400).json({ error: "Invalid RigoToken" })
|
575
|
+
}
|
576
|
+
|
577
|
+
const filePrefix = `courses/${slug}/`
|
578
|
+
|
579
|
+
try {
|
580
|
+
const [files] = await bucket.getFiles({ prefix: filePrefix })
|
581
|
+
|
582
|
+
if (files.length === 0) {
|
583
|
+
return res
|
584
|
+
.status(404)
|
585
|
+
.json({ error: "No package found with the given slug" })
|
586
|
+
}
|
587
|
+
|
588
|
+
await Promise.all(files.map(file => file.delete()))
|
589
|
+
|
590
|
+
console.log(`✅ Successfully deleted package from GCP: ${slug}`)
|
591
|
+
|
592
|
+
return res.json({ message: `Package ${slug} deleted successfully` })
|
593
|
+
} catch (error) {
|
594
|
+
console.error("❌ Error deleting package:", error)
|
595
|
+
return res.status(500).json({ error: "Failed to delete the package" })
|
596
|
+
}
|
597
|
+
})
|
598
|
+
|
599
|
+
app.get("/proxy", async (req, res) => {
|
600
|
+
const { url } = req.query
|
601
|
+
|
602
|
+
if (!url) {
|
603
|
+
return res.status(400).json({ error: "URL is required" })
|
604
|
+
}
|
605
|
+
|
606
|
+
try {
|
607
|
+
const decodedUrl = Buffer.from(url as string, "base64url").toString(
|
608
|
+
"utf-8"
|
609
|
+
)
|
610
|
+
|
611
|
+
const response = await axios.get(decodedUrl, {
|
612
|
+
responseType: "arraybuffer",
|
613
|
+
})
|
614
|
+
|
615
|
+
res.set(response.headers)
|
616
|
+
res.status(response.status).send(response.data)
|
617
|
+
} catch (error) {
|
618
|
+
console.error("Error in /proxy:", error)
|
619
|
+
res.status(500).json({ error: "Failed to fetch the resource" })
|
620
|
+
}
|
621
|
+
})
|
622
|
+
|
597
623
|
app.listen(PORT, () => {
|
598
624
|
console.log(
|
599
625
|
`🚀 Creator UI server running at http://localhost:${PORT}/creator`
|
@@ -13,6 +13,7 @@
|
|
13
13
|
"axios": "^1.8.4",
|
14
14
|
"framer-motion": "^12.9.2",
|
15
15
|
"front-matter": "^4.0.2",
|
16
|
+
"html2canvas": "^1.4.1",
|
16
17
|
"js-yaml": "^4.1.0",
|
17
18
|
"mammoth": "^1.9.0",
|
18
19
|
"mitt": "^3.0.1",
|
@@ -2216,6 +2217,15 @@
|
|
2216
2217
|
"dev": true,
|
2217
2218
|
"license": "MIT"
|
2218
2219
|
},
|
2220
|
+
"node_modules/base64-arraybuffer": {
|
2221
|
+
"version": "1.0.2",
|
2222
|
+
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
2223
|
+
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
2224
|
+
"license": "MIT",
|
2225
|
+
"engines": {
|
2226
|
+
"node": ">= 0.6.0"
|
2227
|
+
}
|
2228
|
+
},
|
2219
2229
|
"node_modules/base64-js": {
|
2220
2230
|
"version": "1.5.1",
|
2221
2231
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
@@ -2504,6 +2514,15 @@
|
|
2504
2514
|
"node": ">= 8"
|
2505
2515
|
}
|
2506
2516
|
},
|
2517
|
+
"node_modules/css-line-break": {
|
2518
|
+
"version": "2.1.0",
|
2519
|
+
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
2520
|
+
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
2521
|
+
"license": "MIT",
|
2522
|
+
"dependencies": {
|
2523
|
+
"utrie": "^1.0.2"
|
2524
|
+
}
|
2525
|
+
},
|
2507
2526
|
"node_modules/csstype": {
|
2508
2527
|
"version": "3.1.3",
|
2509
2528
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
@@ -3557,6 +3576,19 @@
|
|
3557
3576
|
"url": "https://opencollective.com/unified"
|
3558
3577
|
}
|
3559
3578
|
},
|
3579
|
+
"node_modules/html2canvas": {
|
3580
|
+
"version": "1.4.1",
|
3581
|
+
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
3582
|
+
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
3583
|
+
"license": "MIT",
|
3584
|
+
"dependencies": {
|
3585
|
+
"css-line-break": "^2.1.0",
|
3586
|
+
"text-segmentation": "^1.0.3"
|
3587
|
+
},
|
3588
|
+
"engines": {
|
3589
|
+
"node": ">=8.0.0"
|
3590
|
+
}
|
3591
|
+
},
|
3560
3592
|
"node_modules/http-proxy-agent": {
|
3561
3593
|
"version": "5.0.0",
|
3562
3594
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
@@ -5790,6 +5822,15 @@
|
|
5790
5822
|
"uuid": "dist/bin/uuid"
|
5791
5823
|
}
|
5792
5824
|
},
|
5825
|
+
"node_modules/text-segmentation": {
|
5826
|
+
"version": "1.0.3",
|
5827
|
+
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
5828
|
+
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
5829
|
+
"license": "MIT",
|
5830
|
+
"dependencies": {
|
5831
|
+
"utrie": "^1.0.2"
|
5832
|
+
}
|
5833
|
+
},
|
5793
5834
|
"node_modules/to-regex-range": {
|
5794
5835
|
"version": "5.0.1",
|
5795
5836
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
@@ -6050,6 +6091,15 @@
|
|
6050
6091
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
6051
6092
|
"license": "MIT"
|
6052
6093
|
},
|
6094
|
+
"node_modules/utrie": {
|
6095
|
+
"version": "1.0.2",
|
6096
|
+
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
6097
|
+
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
6098
|
+
"license": "MIT",
|
6099
|
+
"dependencies": {
|
6100
|
+
"base64-arraybuffer": "^1.0.2"
|
6101
|
+
}
|
6102
|
+
},
|
6053
6103
|
"node_modules/uuid": {
|
6054
6104
|
"version": "8.3.2",
|
6055
6105
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
package/src/creator/package.json
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
import React, { useEffect } from "react"
|
2
|
+
import html2canvas from "html2canvas"
|
3
|
+
|
4
|
+
import useStore from "../utils/store"
|
5
|
+
import { uploadImageToBucket } from "../utils/lib"
|
6
|
+
import { slugify } from "../utils/creatorUtils"
|
7
|
+
import { eventBus } from "../utils/eventBus"
|
8
|
+
// import { SVGS } from "../assets/svgs"
|
9
|
+
|
10
|
+
function proxify(link: string) {
|
11
|
+
// Validar que el enlace no sea vacío
|
12
|
+
if (!link) {
|
13
|
+
throw new Error("El enlace es requerido")
|
14
|
+
}
|
15
|
+
|
16
|
+
const encodedUrl = btoa(link)
|
17
|
+
|
18
|
+
return `http://localhost:3000/proxy?url=${encodedUrl}`
|
19
|
+
}
|
20
|
+
|
21
|
+
const PreviewGenerator: React.FC = () => {
|
22
|
+
const history = useStore((state) => state.history)
|
23
|
+
const auth = useStore((state) => state.auth)
|
24
|
+
|
25
|
+
const syllabus = history[history.length - 1]
|
26
|
+
|
27
|
+
useEffect(() => {
|
28
|
+
const handleDownload = () => {
|
29
|
+
eventBus.emit("course-generation", {
|
30
|
+
message: "📷 Generating preview image...",
|
31
|
+
})
|
32
|
+
const previewElement = document.getElementById("preview")
|
33
|
+
if (previewElement) {
|
34
|
+
html2canvas(previewElement, {
|
35
|
+
useCORS: true,
|
36
|
+
}).then(async (canvas) => {
|
37
|
+
// const anchor = document.createElement("a")
|
38
|
+
// anchor.href = canvas.toDataURL("image/png")
|
39
|
+
// anchor.download = "preview.png"
|
40
|
+
// anchor.click()
|
41
|
+
|
42
|
+
const imageUrl = canvas.toDataURL("image/png")
|
43
|
+
|
44
|
+
await uploadImageToBucket(
|
45
|
+
imageUrl,
|
46
|
+
`courses/${slugify(syllabus.courseInfo?.title || "")}/preview.png`
|
47
|
+
)
|
48
|
+
eventBus.emit("course-generation", {
|
49
|
+
message: "✅ Preview image generated and uploaded to course.",
|
50
|
+
})
|
51
|
+
})
|
52
|
+
}
|
53
|
+
}
|
54
|
+
handleDownload()
|
55
|
+
}, [])
|
56
|
+
|
57
|
+
return (
|
58
|
+
<div className="fixed">
|
59
|
+
<div
|
60
|
+
id="preview"
|
61
|
+
style={{
|
62
|
+
width: "1000px",
|
63
|
+
height: "630px",
|
64
|
+
background: "white",
|
65
|
+
}}
|
66
|
+
>
|
67
|
+
<div className="bg-learnpack-blue p-4 rounded-md" />
|
68
|
+
<div className="px-4 -mt-5">
|
69
|
+
<h1 className="text-2xl font-bold">{syllabus.courseInfo?.title}</h1>
|
70
|
+
<p className="mt-5 text-sm">{syllabus.courseInfo?.description}</p>
|
71
|
+
<div className="flex items-center gap-2 mt-5">
|
72
|
+
<img
|
73
|
+
src={proxify(auth.user?.profile?.avatar_url || "")}
|
74
|
+
alt="Profile"
|
75
|
+
className="w-10 h-10 rounded-full mt-3"
|
76
|
+
/>
|
77
|
+
<div>
|
78
|
+
<p className=" text-sm font-bold">
|
79
|
+
Author: {auth.user?.first_name} {auth.user?.last_name}
|
80
|
+
</p>
|
81
|
+
<small className=" text-sm">
|
82
|
+
Published on: {new Date().toLocaleDateString()}
|
83
|
+
</small>
|
84
|
+
</div>
|
85
|
+
<div className="ml-auto">
|
86
|
+
<img src="logo.png" className="w-10 h-10" />
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
)
|
93
|
+
}
|
94
|
+
|
95
|
+
export default PreviewGenerator
|
@@ -28,6 +28,7 @@ import { Sidebar } from "./Sidebar"
|
|
28
28
|
import Login from "../Login"
|
29
29
|
import { eventBus } from "../../utils/eventBus"
|
30
30
|
import { useNavigate } from "react-router"
|
31
|
+
import PreviewGenerator from "../PreviewGenerator"
|
31
32
|
|
32
33
|
const SyllabusEditor: React.FC = () => {
|
33
34
|
const navigate = useNavigate()
|
@@ -115,6 +116,7 @@ const SyllabusEditor: React.FC = () => {
|
|
115
116
|
courseInfo: {
|
116
117
|
...syllabus.courseInfo,
|
117
118
|
title: res.parsed.title || syllabus.courseInfo.title,
|
119
|
+
description: res.parsed.description || syllabus.courseInfo.description,
|
118
120
|
},
|
119
121
|
})
|
120
122
|
setMessages((prev) => {
|
@@ -135,6 +137,7 @@ const SyllabusEditor: React.FC = () => {
|
|
135
137
|
toast.error("Please provide a title for the course")
|
136
138
|
return
|
137
139
|
}
|
140
|
+
setIsGenerating(true)
|
138
141
|
|
139
142
|
let tokenToUse = auth.rigoToken
|
140
143
|
const onValidRigoToken = (rigotoken: string) => {
|
@@ -201,14 +204,19 @@ const SyllabusEditor: React.FC = () => {
|
|
201
204
|
|
202
205
|
if (!syllabus) return null
|
203
206
|
|
207
|
+
console.log(auth.user)
|
208
|
+
|
204
209
|
return isGenerating ? (
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
+
<>
|
211
|
+
<Loader
|
212
|
+
listeningTo="course-generation"
|
213
|
+
icon={<img src={"rigo-float.gif"} alt="rigo" className="w-20 h-20" />}
|
214
|
+
initialBuffer="🚀 Starting course generation..."
|
215
|
+
text="Learnpack is setting up your tutorial.
|
210
216
|
It may take a moment..."
|
211
|
-
|
217
|
+
/>
|
218
|
+
<PreviewGenerator />
|
219
|
+
</>
|
212
220
|
) : (
|
213
221
|
<div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
|
214
222
|
{showLoginModal && (
|
package/src/creator/src/main.tsx
CHANGED
@@ -5,6 +5,7 @@ import "./index.css"
|
|
5
5
|
import App from "./App.tsx"
|
6
6
|
import SyllabusEditor from "./components/syllabus/SyllabusEditor.tsx"
|
7
7
|
import { Toaster } from "react-hot-toast"
|
8
|
+
import PreviewGenerator from "./components/PreviewGenerator.tsx"
|
8
9
|
createRoot(document.getElementById("root")!).render(
|
9
10
|
<StrictMode>
|
10
11
|
<Toaster />
|
@@ -12,6 +13,7 @@ createRoot(document.getElementById("root")!).render(
|
|
12
13
|
<Routes>
|
13
14
|
<Route path="/creator" element={<App />} />
|
14
15
|
<Route path="/creator/syllabus" element={<SyllabusEditor />} />
|
16
|
+
<Route path="/creator/image" element={<PreviewGenerator />} />
|
15
17
|
</Routes>
|
16
18
|
</BrowserRouter>
|
17
19
|
</StrictMode>
|