@learnpack/learnpack 5.0.306 → 5.0.308
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/publish.js +6 -2
- package/lib/commands/serve.js +105 -30
- package/lib/creatorDist/assets/{index-CTQfJYti.js → index-B37w_ZhT.js} +9248 -9158
- package/lib/creatorDist/assets/{index-DTjdV1LF.css → index-D4SYZg0r.css} +3 -0
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.d.ts +4 -1
- package/lib/utils/api.js +3 -3
- package/package.json +1 -1
- package/src/commands/publish.ts +20 -22
- package/src/commands/serve.ts +152 -56
- package/src/creator/src/App.tsx +49 -10
- package/src/creator/src/components/LanguageDetectionModal.tsx +46 -0
- package/src/creator/src/components/ParamsChecker.tsx +11 -3
- package/src/creator/src/locales/en.json +11 -0
- package/src/creator/src/locales/es.json +11 -0
- package/src/creator/src/utils/lib.ts +172 -171
- package/src/creatorDist/assets/{index-CTQfJYti.js → index-B37w_ZhT.js} +9248 -9158
- package/src/creatorDist/assets/{index-DTjdV1LF.css → index-D4SYZg0r.css} +3 -0
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +2829 -2801
- package/src/ui/_app/learnpack.svg +7 -7
- package/src/ui/_app/sw.js +59 -59
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +38 -42
|
@@ -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-B37w_ZhT.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/creator/assets/index-D4SYZg0r.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
package/lib/utils/api.d.ts
CHANGED
|
@@ -59,7 +59,10 @@ declare const _default: {
|
|
|
59
59
|
}>;
|
|
60
60
|
updateAsset: (token: string, assetSlug: string, asset: Partial<TAssetMissing>) => Promise<any>;
|
|
61
61
|
getCategories: (token: string) => Promise<any>;
|
|
62
|
-
|
|
62
|
+
updateRigoPackage: (token: string, slug: string, updates: {
|
|
63
|
+
asset_id?: number;
|
|
64
|
+
new_slug?: string;
|
|
65
|
+
}) => Promise<any>;
|
|
63
66
|
createRigoPackage: (token: string, slug: string, config: any) => Promise<any>;
|
|
64
67
|
getCurrentTechnologies: () => TTechnology[];
|
|
65
68
|
updateTechnologiesPeriodically: typeof updateTechnologiesPeriodically;
|
package/lib/utils/api.js
CHANGED
|
@@ -420,11 +420,11 @@ const getCategories = async (token) => {
|
|
|
420
420
|
throw error;
|
|
421
421
|
}
|
|
422
422
|
};
|
|
423
|
-
const
|
|
423
|
+
const updateRigoPackage = async (token, slug, updates) => {
|
|
424
424
|
const cleanToken = token.replace(/[\n\r]/g, "");
|
|
425
425
|
const url = `${exports.RIGOBOT_HOST}/v1/learnpack/package/${slug}/`;
|
|
426
426
|
try {
|
|
427
|
-
const response = await axios_1.default.put(url,
|
|
427
|
+
const response = await axios_1.default.put(url, updates, {
|
|
428
428
|
headers: {
|
|
429
429
|
Authorization: "Token " + cleanToken,
|
|
430
430
|
},
|
|
@@ -516,7 +516,7 @@ exports.default = {
|
|
|
516
516
|
doesAssetExists: exports.doesAssetExists,
|
|
517
517
|
updateAsset,
|
|
518
518
|
getCategories,
|
|
519
|
-
|
|
519
|
+
updateRigoPackage,
|
|
520
520
|
createRigoPackage,
|
|
521
521
|
getCurrentTechnologies: exports.getCurrentTechnologies,
|
|
522
522
|
updateTechnologiesPeriodically,
|
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.308",
|
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
|
6
6
|
"contributors": [
|
|
7
7
|
{
|
package/src/commands/publish.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs from "fs"
|
|
|
8
8
|
import * as path from "path"
|
|
9
9
|
import * as archiver from "archiver"
|
|
10
10
|
import axios from "axios"
|
|
11
|
-
import FormData = require("form-data")
|
|
11
|
+
import FormData = require("form-data")
|
|
12
12
|
import Console from "../utils/console"
|
|
13
13
|
import {
|
|
14
14
|
decompress,
|
|
@@ -60,11 +60,9 @@ export const handleAssetCreation = async (
|
|
|
60
60
|
readme_raw: b64IndexReadme,
|
|
61
61
|
all_translations,
|
|
62
62
|
})
|
|
63
|
-
await api.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
asset.id
|
|
67
|
-
)
|
|
63
|
+
await api.updateRigoPackage(sessionPayload.token, learnJson.slug, {
|
|
64
|
+
asset_id: asset.id,
|
|
65
|
+
})
|
|
68
66
|
Console.info("Asset created with id", asset.id)
|
|
69
67
|
return asset
|
|
70
68
|
}
|
|
@@ -77,10 +75,12 @@ export const handleAssetCreation = async (
|
|
|
77
75
|
description: learnJson.description[selectedLang],
|
|
78
76
|
all_translations,
|
|
79
77
|
})
|
|
80
|
-
await api.
|
|
78
|
+
await api.updateRigoPackage(
|
|
81
79
|
sessionPayload.rigobotToken.trim(),
|
|
82
80
|
learnJson.slug,
|
|
83
|
-
|
|
81
|
+
{
|
|
82
|
+
asset_id: asset.id,
|
|
83
|
+
}
|
|
84
84
|
)
|
|
85
85
|
Console.info("Asset updated with id", asset.id)
|
|
86
86
|
return asset
|
|
@@ -117,19 +117,19 @@ const runAudit = (strict: boolean) => {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
type Academy = {
|
|
120
|
-
id: number
|
|
121
|
-
name: string
|
|
122
|
-
slug?: string
|
|
123
|
-
timezone?: string
|
|
124
|
-
}
|
|
120
|
+
id: number
|
|
121
|
+
name: string
|
|
122
|
+
slug?: string
|
|
123
|
+
timezone?: string
|
|
124
|
+
}
|
|
125
125
|
|
|
126
126
|
type Category = {
|
|
127
|
-
id: number
|
|
128
|
-
slug: string
|
|
129
|
-
title: string
|
|
130
|
-
lang: string
|
|
131
|
-
academy: Academy
|
|
132
|
-
}
|
|
127
|
+
id: number
|
|
128
|
+
slug: string
|
|
129
|
+
title: string
|
|
130
|
+
lang: string
|
|
131
|
+
academy: Academy
|
|
132
|
+
}
|
|
133
133
|
|
|
134
134
|
function getCategoriesByAcademy(
|
|
135
135
|
categories: Category[],
|
|
@@ -292,9 +292,7 @@ class BuildCommand extends SessionCommand {
|
|
|
292
292
|
|
|
293
293
|
if (configObject) {
|
|
294
294
|
const { config } = configObject
|
|
295
|
-
const appAlreadyExists = checkIfDirectoryExists(
|
|
296
|
-
`${config?.dirPath}/_app`
|
|
297
|
-
)
|
|
295
|
+
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
|
298
296
|
|
|
299
297
|
if (!appAlreadyExists) {
|
|
300
298
|
// download app and decompress
|
package/src/commands/serve.ts
CHANGED
|
@@ -20,7 +20,6 @@ import * as archiver from "archiver"
|
|
|
20
20
|
import * as mkdirp from "mkdirp"
|
|
21
21
|
import { convert } from "html-to-text"
|
|
22
22
|
import * as rimraf from "rimraf"
|
|
23
|
-
import { v4 as uuidv4 } from "uuid"
|
|
24
23
|
import SessionCommand from "../utils/SessionCommand"
|
|
25
24
|
import { Bucket, Storage } from "@google-cloud/storage"
|
|
26
25
|
import { downloadEditor, decompress } from "../managers/file"
|
|
@@ -44,7 +43,6 @@ import * as dotenv from "dotenv"
|
|
|
44
43
|
|
|
45
44
|
// import { v4 as uuidv4 } from "uuid"
|
|
46
45
|
import {
|
|
47
|
-
extractImagesFromMarkdown,
|
|
48
46
|
getFilenameFromUrl,
|
|
49
47
|
getReadmeExtension,
|
|
50
48
|
} from "../utils/creatorUtilities"
|
|
@@ -52,11 +50,8 @@ import {
|
|
|
52
50
|
import axios from "axios"
|
|
53
51
|
import * as FormData from "form-data"
|
|
54
52
|
import api, { RIGOBOT_HOST, RIGOBOT_REALTIME_HOST } from "../utils/api"
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
minutesToISO8601Duration,
|
|
58
|
-
} from "../utils/misc"
|
|
59
|
-
import { buildConfig, ConfigResponse } from "../utils/configBuilder"
|
|
53
|
+
import { createUploadMiddleware, minutesToISO8601Duration } from "../utils/misc"
|
|
54
|
+
import { buildConfig } from "../utils/configBuilder"
|
|
60
55
|
import { checkReadability, slugify } from "../utils/creatorUtilities"
|
|
61
56
|
import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
|
|
62
57
|
import { handleAssetCreation } from "./publish"
|
|
@@ -67,20 +62,6 @@ const frontMatter = require("front-matter")
|
|
|
67
62
|
|
|
68
63
|
dotenv.config()
|
|
69
64
|
|
|
70
|
-
// Asegúrate de tener uuid instalado
|
|
71
|
-
// npm install uuid
|
|
72
|
-
|
|
73
|
-
function findLast<T>(
|
|
74
|
-
array: T[],
|
|
75
|
-
predicate: (item: T) => boolean
|
|
76
|
-
): T | undefined {
|
|
77
|
-
for (let i = array.length - 1; i >= 0; i--) {
|
|
78
|
-
if (predicate(array[i])) return array[i]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return undefined
|
|
82
|
-
}
|
|
83
|
-
|
|
84
65
|
export const createLearnJson = (courseInfo: FormState) => {
|
|
85
66
|
// console.log("courseInfo to create learn json", courseInfo)
|
|
86
67
|
|
|
@@ -687,7 +668,6 @@ const fixPreviewUrl = (slug: string, previewUrl: string) => {
|
|
|
687
668
|
}
|
|
688
669
|
|
|
689
670
|
const expectedUrl = `https://${slug}.learn-pack.com/preview.png`
|
|
690
|
-
console.log("Preview url fixed!", expectedUrl)
|
|
691
671
|
return expectedUrl
|
|
692
672
|
}
|
|
693
673
|
|
|
@@ -713,13 +693,11 @@ export default class ServeCommand extends SessionCommand {
|
|
|
713
693
|
|
|
714
694
|
async init() {
|
|
715
695
|
const { flags } = this.parse(ServeCommand)
|
|
716
|
-
console.log("Initializing serve command")
|
|
717
696
|
}
|
|
718
697
|
|
|
719
698
|
async run() {
|
|
720
699
|
const crendsEnv = process.env.GCP_CREDENTIALS_JSON
|
|
721
700
|
if (!crendsEnv) {
|
|
722
|
-
console.log("GCP_CREDENTIALS_JSON is not set")
|
|
723
701
|
process.exit(1)
|
|
724
702
|
}
|
|
725
703
|
|
|
@@ -737,10 +715,12 @@ export default class ServeCommand extends SessionCommand {
|
|
|
737
715
|
const host = process.env.HOST
|
|
738
716
|
|
|
739
717
|
if (!host) {
|
|
740
|
-
console.log(
|
|
718
|
+
console.log(
|
|
719
|
+
"ERROR: HOST is not set, please set the HOST environment variable"
|
|
720
|
+
)
|
|
741
721
|
process.exit(1)
|
|
742
722
|
} else {
|
|
743
|
-
console.log("HOST is set to", host)
|
|
723
|
+
console.log("INFO: HOST is set to", host)
|
|
744
724
|
}
|
|
745
725
|
|
|
746
726
|
// async function listFilesWithPrefix(prefix: string) {
|
|
@@ -807,7 +787,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
807
787
|
})
|
|
808
788
|
|
|
809
789
|
stream.on("finish", () => {
|
|
810
|
-
console.log(
|
|
790
|
+
console.log(`INFO: Uploaded to: ${file.name}`)
|
|
811
791
|
res.send("File uploaded successfully")
|
|
812
792
|
})
|
|
813
793
|
|
|
@@ -817,7 +797,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
817
797
|
const upload = createUploadMiddleware()
|
|
818
798
|
|
|
819
799
|
app.post("/upload-image-file", upload.single("file"), async (req, res) => {
|
|
820
|
-
console.log("
|
|
800
|
+
console.log("INFO: Uploading image file")
|
|
821
801
|
|
|
822
802
|
const destination = (req.body as { destination?: string }).destination
|
|
823
803
|
|
|
@@ -842,7 +822,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
842
822
|
contentType: req.file.mimetype,
|
|
843
823
|
})
|
|
844
824
|
|
|
845
|
-
console.log(
|
|
825
|
+
console.log(`INFO: Image uploaded to ${file.name}`)
|
|
846
826
|
res.json({ message: "Image uploaded successfully", path: file.name })
|
|
847
827
|
} catch (error) {
|
|
848
828
|
console.error("❌ upload-image-file error:", error)
|
|
@@ -1075,9 +1055,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1075
1055
|
fileObj.solution,
|
|
1076
1056
|
solutionFilePath
|
|
1077
1057
|
)
|
|
1078
|
-
console.log(
|
|
1079
|
-
`✅ Saved solution file: ${solutionFilePath}`
|
|
1080
|
-
)
|
|
1058
|
+
console.log(`✅ Saved solution file: ${solutionFilePath}`)
|
|
1081
1059
|
} else {
|
|
1082
1060
|
// If no extension, just add .solution.hide
|
|
1083
1061
|
const solutionFileName = `${fileObj.name}.solution.hide`
|
|
@@ -1088,9 +1066,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1088
1066
|
fileObj.solution,
|
|
1089
1067
|
solutionFilePath
|
|
1090
1068
|
)
|
|
1091
|
-
console.log(
|
|
1092
|
-
`✅ Saved solution file: ${solutionFilePath}`
|
|
1093
|
-
)
|
|
1069
|
+
console.log(`✅ Saved solution file: ${solutionFilePath}`)
|
|
1094
1070
|
}
|
|
1095
1071
|
}
|
|
1096
1072
|
} catch (parseError) {
|
|
@@ -1976,10 +1952,6 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1976
1952
|
|
|
1977
1953
|
try {
|
|
1978
1954
|
const { config, exercises } = await buildConfig(bucket, courseSlug)
|
|
1979
|
-
|
|
1980
|
-
console.log("CONFIG", config)
|
|
1981
|
-
console.log("EXERCISES", exercises)
|
|
1982
|
-
|
|
1983
1955
|
res.set("X-Creator-Web", "true")
|
|
1984
1956
|
res.set("Access-Control-Expose-Headers", "X-Creator-Web")
|
|
1985
1957
|
|
|
@@ -2221,11 +2193,11 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2221
2193
|
new_languages: neededLanguagesList.join(","),
|
|
2222
2194
|
})
|
|
2223
2195
|
|
|
2224
|
-
const
|
|
2225
|
-
const
|
|
2196
|
+
const translatedTitle = JSON.parse(result.parsed.title)
|
|
2197
|
+
const translatedDescription = JSON.parse(result.parsed.description)
|
|
2226
2198
|
|
|
2227
|
-
courseJson.title =
|
|
2228
|
-
courseJson.description =
|
|
2199
|
+
courseJson.title = translatedTitle
|
|
2200
|
+
courseJson.description = translatedDescription
|
|
2229
2201
|
|
|
2230
2202
|
await uploadFileToBucket(
|
|
2231
2203
|
bucket,
|
|
@@ -2593,10 +2565,12 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2593
2565
|
.json({ error: "Authentication failed, missing tokens" })
|
|
2594
2566
|
}
|
|
2595
2567
|
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
slug
|
|
2568
|
+
const configFile = await bucket.file(
|
|
2569
|
+
`courses/${slug}/.learn/config.json`
|
|
2599
2570
|
)
|
|
2571
|
+
const [configContent] = await configFile.download()
|
|
2572
|
+
const configJson = JSON.parse(configContent.toString())
|
|
2573
|
+
const { config, exercises } = configJson
|
|
2600
2574
|
|
|
2601
2575
|
const prefix = `courses/${slug}/`
|
|
2602
2576
|
const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`)
|
|
@@ -2838,9 +2812,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2838
2812
|
})
|
|
2839
2813
|
} catch (error: any) {
|
|
2840
2814
|
console.error("❌ /actions/fetch error:", error.message || error)
|
|
2841
|
-
res
|
|
2842
|
-
.status(500)
|
|
2843
|
-
.json({ error: error.message || "Failed to fetch link" })
|
|
2815
|
+
res.status(500).json({ error: error.message || "Failed to fetch link" })
|
|
2844
2816
|
}
|
|
2845
2817
|
})
|
|
2846
2818
|
app.delete("/packages/:slug", async (req, res) => {
|
|
@@ -2881,6 +2853,135 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2881
2853
|
}
|
|
2882
2854
|
})
|
|
2883
2855
|
|
|
2856
|
+
app.post("/actions/change-slug", async (req, res) => {
|
|
2857
|
+
const { currentSlug, newSlug } = req.body
|
|
2858
|
+
const rigoToken = req.header("x-rigo-token")
|
|
2859
|
+
|
|
2860
|
+
if (!rigoToken) {
|
|
2861
|
+
return res.status(400).json({
|
|
2862
|
+
error: "Rigo token is required. x-rigo-token header is missing",
|
|
2863
|
+
})
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
if (!currentSlug || !newSlug) {
|
|
2867
|
+
return res.status(400).json({
|
|
2868
|
+
error: "Both currentSlug and newSlug are required",
|
|
2869
|
+
})
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
if (currentSlug === newSlug) {
|
|
2873
|
+
return res.status(200).json({
|
|
2874
|
+
message: "Slug unchanged",
|
|
2875
|
+
newSlug: currentSlug,
|
|
2876
|
+
})
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
try {
|
|
2880
|
+
// Check if new slug is available via RigoBot
|
|
2881
|
+
const isAvailable = await isValidRigoToken(rigoToken)
|
|
2882
|
+
if (!isAvailable) {
|
|
2883
|
+
return res.status(401).json({ error: "Invalid Rigo token" })
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
// Check slug availability
|
|
2887
|
+
const slugCheckUrl = `${RIGOBOT_HOST}/v1/learnpack/check-slug-availability?slug=${encodeURIComponent(
|
|
2888
|
+
newSlug
|
|
2889
|
+
)}`
|
|
2890
|
+
const slugResponse = await axios.get(slugCheckUrl)
|
|
2891
|
+
|
|
2892
|
+
if (!slugResponse.data.available) {
|
|
2893
|
+
return res.status(409).json({
|
|
2894
|
+
error: "Slug is not available",
|
|
2895
|
+
available: false,
|
|
2896
|
+
})
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// Get all files with current slug prefix
|
|
2900
|
+
const currentPrefix = `courses/${currentSlug}/`
|
|
2901
|
+
const [files] = await bucket.getFiles({ prefix: currentPrefix })
|
|
2902
|
+
|
|
2903
|
+
if (files.length === 0) {
|
|
2904
|
+
return res.status(404).json({
|
|
2905
|
+
error: "No files found for current slug",
|
|
2906
|
+
})
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
// Copy all files to new slug location
|
|
2910
|
+
const newPrefix = `courses/${newSlug}/`
|
|
2911
|
+
|
|
2912
|
+
for (const file of files) {
|
|
2913
|
+
const newFileName = file.name.replace(currentPrefix, newPrefix)
|
|
2914
|
+
// eslint-disable-next-line no-await-in-loop
|
|
2915
|
+
await file.copy(newFileName)
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
// Update learn.json with new slug
|
|
2919
|
+
const learnJsonFile = bucket.file(`${newPrefix}learn.json`)
|
|
2920
|
+
const [learnJsonContent] = await learnJsonFile.download()
|
|
2921
|
+
const learnJson = JSON.parse(learnJsonContent.toString())
|
|
2922
|
+
learnJson.slug = newSlug
|
|
2923
|
+
await uploadFileToBucket(
|
|
2924
|
+
bucket,
|
|
2925
|
+
JSON.stringify(learnJson),
|
|
2926
|
+
`${newPrefix}learn.json`
|
|
2927
|
+
)
|
|
2928
|
+
|
|
2929
|
+
// Update initialSyllabus.json with new slug
|
|
2930
|
+
const syllabusFile = bucket.file(
|
|
2931
|
+
`${newPrefix}.learn/initialSyllabus.json`
|
|
2932
|
+
)
|
|
2933
|
+
const [syllabusContent] = await syllabusFile.download()
|
|
2934
|
+
const syllabus = JSON.parse(syllabusContent.toString())
|
|
2935
|
+
syllabus.courseInfo.slug = newSlug
|
|
2936
|
+
await uploadFileToBucket(
|
|
2937
|
+
bucket,
|
|
2938
|
+
JSON.stringify(syllabus),
|
|
2939
|
+
`${newPrefix}.learn/initialSyllabus.json`
|
|
2940
|
+
)
|
|
2941
|
+
|
|
2942
|
+
// Update config.json with new slug
|
|
2943
|
+
const configFile = bucket.file(`${newPrefix}.learn/config.json`)
|
|
2944
|
+
const [configContent] = await configFile.download()
|
|
2945
|
+
const config = JSON.parse(configContent.toString())
|
|
2946
|
+
config.config.slug = newSlug
|
|
2947
|
+
await uploadFileToBucket(
|
|
2948
|
+
bucket,
|
|
2949
|
+
JSON.stringify(config),
|
|
2950
|
+
`${newPrefix}.learn/config.json`
|
|
2951
|
+
)
|
|
2952
|
+
|
|
2953
|
+
// Update Rigobot package slug
|
|
2954
|
+
const updateUrl = `${RIGOBOT_HOST}/v1/learnpack/package/${currentSlug}/`
|
|
2955
|
+
await axios.put(
|
|
2956
|
+
updateUrl,
|
|
2957
|
+
{ new_slug: newSlug },
|
|
2958
|
+
{
|
|
2959
|
+
headers: {
|
|
2960
|
+
Authorization: "Token " + rigoToken.trim(),
|
|
2961
|
+
},
|
|
2962
|
+
}
|
|
2963
|
+
)
|
|
2964
|
+
|
|
2965
|
+
// Delete old files
|
|
2966
|
+
await Promise.all(files.map(file => file.delete()))
|
|
2967
|
+
|
|
2968
|
+
console.log(
|
|
2969
|
+
`✅ Successfully changed slug from ${currentSlug} to ${newSlug}`
|
|
2970
|
+
)
|
|
2971
|
+
|
|
2972
|
+
return res.json({
|
|
2973
|
+
message: "Slug changed successfully",
|
|
2974
|
+
newSlug: newSlug,
|
|
2975
|
+
})
|
|
2976
|
+
} catch (error) {
|
|
2977
|
+
console.error("❌ Error changing slug:", error)
|
|
2978
|
+
return res.status(500).json({
|
|
2979
|
+
error: "Failed to change slug",
|
|
2980
|
+
details: (error as Error).message,
|
|
2981
|
+
})
|
|
2982
|
+
}
|
|
2983
|
+
})
|
|
2984
|
+
|
|
2884
2985
|
app.get("/proxy", async (req, res) => {
|
|
2885
2986
|
const { url } = req.query
|
|
2886
2987
|
|
|
@@ -3020,10 +3121,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
3020
3121
|
!metadata.rights ||
|
|
3021
3122
|
!metadata.lang
|
|
3022
3123
|
) {
|
|
3023
|
-
console.log(
|
|
3024
|
-
"Missing required metadata for EPUB export",
|
|
3025
|
-
metadata
|
|
3026
|
-
)
|
|
3124
|
+
console.log("Missing required metadata for EPUB export", metadata)
|
|
3027
3125
|
return res.status(400).json({
|
|
3028
3126
|
error: "Missing required metadata for EPUB export",
|
|
3029
3127
|
required: ["creator", "publisher", "title", "rights", "lang"],
|
|
@@ -3068,9 +3166,7 @@ export default class ServeCommand extends SessionCommand {
|
|
|
3068
3166
|
})
|
|
3069
3167
|
} catch (error: any) {
|
|
3070
3168
|
console.error("Export error:", error)
|
|
3071
|
-
res
|
|
3072
|
-
.status(500)
|
|
3073
|
-
.json({ error: "Export failed", details: error.message })
|
|
3169
|
+
res.status(500).json({ error: "Export failed", details: error.message })
|
|
3074
3170
|
}
|
|
3075
3171
|
})
|
|
3076
3172
|
|
package/src/creator/src/App.tsx
CHANGED
|
@@ -31,6 +31,7 @@ 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 LanguageDetectionModal from "./components/LanguageDetectionModal"
|
|
34
35
|
|
|
35
36
|
function App() {
|
|
36
37
|
const navigate = useNavigate()
|
|
@@ -72,6 +73,8 @@ function App() {
|
|
|
72
73
|
|
|
73
74
|
const [notificationId, setNotificationId] = useState<string>("")
|
|
74
75
|
const [showTurnstileModal, setShowTurnstileModal] = useState(false)
|
|
76
|
+
const [showLanguageDetectionModal, setShowLanguageDetectionModal] = useState(false)
|
|
77
|
+
const [detectedLanguage, setDetectedLanguage] = useState<string>("")
|
|
75
78
|
|
|
76
79
|
useEffect(() => {
|
|
77
80
|
if (formState.isCompleted) {
|
|
@@ -130,27 +133,45 @@ function App() {
|
|
|
130
133
|
if (duration && !isNaN(parseInt(duration))) {
|
|
131
134
|
if (["15", "30", "60"].includes(duration)) {
|
|
132
135
|
const durationInt = parseInt(duration)
|
|
133
|
-
console.log("duration", durationInt)
|
|
134
136
|
setFormState({
|
|
135
137
|
duration: durationInt,
|
|
136
138
|
})
|
|
137
139
|
} else {
|
|
138
|
-
console.
|
|
140
|
+
console.error("Invalid duration received in params", duration)
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
|
-
|
|
143
|
+
|
|
142
144
|
if (plan) {
|
|
143
145
|
setPlanToRedirect(plan)
|
|
144
146
|
} else {
|
|
145
147
|
console.debug("No plan received in params")
|
|
146
148
|
}
|
|
149
|
+
|
|
150
|
+
// Language detection logic
|
|
151
|
+
let finalLanguage = language
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
// Change if query param is provided
|
|
154
|
+
if (finalLanguage && finalLanguage.length === 2) {
|
|
155
|
+
console.log("LANGUAGE", finalLanguage)
|
|
149
156
|
setFormState({
|
|
150
|
-
language:
|
|
157
|
+
language: finalLanguage,
|
|
151
158
|
})
|
|
159
|
+
if (i18n.language !== finalLanguage) {
|
|
160
|
+
i18n.changeLanguage(finalLanguage)
|
|
161
|
+
}
|
|
152
162
|
}
|
|
153
163
|
|
|
164
|
+
// If no language is provided, detect the language of the description
|
|
165
|
+
if (!finalLanguage && description) {
|
|
166
|
+
const detectedLang = detectLanguage(description)
|
|
167
|
+
if (detectedLang && detectedLang !== i18n.language) {
|
|
168
|
+
finalLanguage = detectedLang
|
|
169
|
+
setDetectedLanguage(detectedLang)
|
|
170
|
+
setShowLanguageDetectionModal(true)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
154
175
|
if (purpose && purpose.length > 0 && possiblePurposes.includes(purpose)) {
|
|
155
176
|
setFormState({
|
|
156
177
|
purpose: purpose,
|
|
@@ -233,8 +254,6 @@ function App() {
|
|
|
233
254
|
formState.purpose || "learnpack-lesson-writer",
|
|
234
255
|
tokenType === "rigo" ? false : true
|
|
235
256
|
)
|
|
236
|
-
console.log("RES", res)
|
|
237
|
-
|
|
238
257
|
setNotificationId(res.notificationId)
|
|
239
258
|
} catch (error) {
|
|
240
259
|
console.error(error, "ERROR CREATING TUTORIAL")
|
|
@@ -254,6 +273,18 @@ function App() {
|
|
|
254
273
|
}
|
|
255
274
|
}
|
|
256
275
|
|
|
276
|
+
const handleLanguageSwitch = () => {
|
|
277
|
+
setFormState({
|
|
278
|
+
language: detectedLanguage,
|
|
279
|
+
})
|
|
280
|
+
i18n.changeLanguage(detectedLanguage)
|
|
281
|
+
setShowLanguageDetectionModal(false)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const handleLanguageStay = () => {
|
|
285
|
+
setShowLanguageDetectionModal(false)
|
|
286
|
+
}
|
|
287
|
+
|
|
257
288
|
const buildSteps = () => {
|
|
258
289
|
const steps = [
|
|
259
290
|
{
|
|
@@ -263,9 +294,11 @@ function App() {
|
|
|
263
294
|
required: true,
|
|
264
295
|
validator: (value: string) => {
|
|
265
296
|
const lang = detectLanguage(value)
|
|
297
|
+
console.log("Description validator language detection:", { lang, currentLang: i18n.language, value })
|
|
266
298
|
|
|
267
|
-
if (lang) {
|
|
268
|
-
|
|
299
|
+
if (lang && lang !== "en" && lang !== i18n.language) {
|
|
300
|
+
setDetectedLanguage(lang)
|
|
301
|
+
setShowLanguageDetectionModal(true)
|
|
269
302
|
}
|
|
270
303
|
|
|
271
304
|
return value.length > 0
|
|
@@ -451,6 +484,13 @@ function App() {
|
|
|
451
484
|
return (
|
|
452
485
|
<>
|
|
453
486
|
<ParamsChecker />
|
|
487
|
+
{showLanguageDetectionModal && (
|
|
488
|
+
<LanguageDetectionModal
|
|
489
|
+
detectedLanguage={detectedLanguage}
|
|
490
|
+
onSwitch={handleLanguageSwitch}
|
|
491
|
+
onStay={handleLanguageStay}
|
|
492
|
+
/>
|
|
493
|
+
)}
|
|
454
494
|
{showTurnstileModal && (
|
|
455
495
|
<TurnstileModal
|
|
456
496
|
onClose={() => {
|
|
@@ -477,7 +517,6 @@ function App() {
|
|
|
477
517
|
{notificationId && (
|
|
478
518
|
<NotificationListener
|
|
479
519
|
onNotification={(res) => {
|
|
480
|
-
console.log("Async response", res)
|
|
481
520
|
const lessons = res.parsed.listOfSteps.map((lesson: any) => {
|
|
482
521
|
return parseLesson(lesson, [])
|
|
483
522
|
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useTranslation } from "react-i18next"
|
|
2
|
+
|
|
3
|
+
interface LanguageDetectionModalProps {
|
|
4
|
+
detectedLanguage: string
|
|
5
|
+
onSwitch: () => void
|
|
6
|
+
onStay: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function LanguageDetectionModal({
|
|
10
|
+
detectedLanguage,
|
|
11
|
+
onSwitch,
|
|
12
|
+
onStay,
|
|
13
|
+
}: LanguageDetectionModalProps) {
|
|
14
|
+
const { t, i18n } = useTranslation()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000">
|
|
18
|
+
<div className="bg-white p-6 rounded-xl shadow-md max-w-md w-full mx-4">
|
|
19
|
+
<h2 className="text-xl font-semibold text-center mb-4">
|
|
20
|
+
{t("languageDetection.title")}
|
|
21
|
+
</h2>
|
|
22
|
+
<p className="text-gray-600 text-center mb-6">
|
|
23
|
+
{t("languageDetection.message", {
|
|
24
|
+
language: t(`languageDetection.languages.${detectedLanguage.toLowerCase()}`)
|
|
25
|
+
})}
|
|
26
|
+
</p>
|
|
27
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
28
|
+
<button
|
|
29
|
+
onClick={onSwitch}
|
|
30
|
+
className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md font-semibold hover:bg-blue-700 transition-colors"
|
|
31
|
+
>
|
|
32
|
+
{t("languageDetection.switchTo", {
|
|
33
|
+
language: t(`languageDetection.languages.${detectedLanguage.toLowerCase()}`)
|
|
34
|
+
})}
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
onClick={onStay}
|
|
38
|
+
className="flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md font-semibold hover:bg-gray-300 transition-colors"
|
|
39
|
+
>
|
|
40
|
+
{t(`languageDetection.stayIn${i18n.language.toLowerCase()}`)}
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|