@learnpack/learnpack 5.0.317 → 5.0.319
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 +149 -1
- package/lib/creatorDist/assets/{index-DRIj_L1d.js → index-XZDcEWl9.js} +4 -3
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/rigoActions.d.ts +1 -1
- package/lib/utils/rigoActions.js +2 -2
- package/package.json +1 -1
- package/src/commands/serve.ts +195 -1
- package/src/creator/src/App.tsx +0 -1
- package/src/creator/src/components/LanguageDetectionModal.tsx +1 -1
- package/src/creatorDist/assets/{index-DRIj_L1d.js → index-XZDcEWl9.js} +4 -3
- package/src/creatorDist/index.html +1 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +1826 -1822
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/rigoActions.ts +639 -638
package/lib/commands/serve.js
CHANGED
|
@@ -185,6 +185,12 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
|
|
|
185
185
|
const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
|
|
186
186
|
console.log("Starting initial content generation for", exSlug);
|
|
187
187
|
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`;
|
|
188
|
+
// Determine if this is the first lesson
|
|
189
|
+
const exerciseIndex = steps.findIndex(lesson => lesson.uid === exercise.uid);
|
|
190
|
+
const isFirstLesson = exerciseIndex === 0;
|
|
191
|
+
const endpointSlug = isFirstLesson ?
|
|
192
|
+
"initial-step-content-generator" :
|
|
193
|
+
"generate-step-initial-content";
|
|
188
194
|
// Emit notification that initial content generation is starting
|
|
189
195
|
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
|
190
196
|
lesson: exSlug,
|
|
@@ -203,7 +209,7 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
|
|
|
203
209
|
output_language: packageContext.language || "en",
|
|
204
210
|
current_syllabus: JSON.stringify(fullSyllabus),
|
|
205
211
|
lesson_description: JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
|
|
206
|
-
}, webhookUrl);
|
|
212
|
+
}, webhookUrl, endpointSlug);
|
|
207
213
|
console.log("INITIAL CONTENT GENERATOR RES", res);
|
|
208
214
|
return res.id;
|
|
209
215
|
}
|
|
@@ -1891,6 +1897,148 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1891
1897
|
});
|
|
1892
1898
|
}
|
|
1893
1899
|
});
|
|
1900
|
+
app.put("/courses/:courseSlug/exercises/:exerciseSlug/file/:filename/rename", express.json(), async (req, res) => {
|
|
1901
|
+
console.log("PUT /courses/:courseSlug/exercises/:exerciseSlug/file/:filename/rename", req.params, req.body);
|
|
1902
|
+
const { courseSlug, exerciseSlug, filename } = req.params;
|
|
1903
|
+
const { oldFilename, newFilename } = req.body;
|
|
1904
|
+
try {
|
|
1905
|
+
// Validaciones
|
|
1906
|
+
if (!oldFilename || !newFilename) {
|
|
1907
|
+
return res.status(400).json({
|
|
1908
|
+
error: "oldFilename and newFilename are required",
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
// Validar que las extensiones coincidan
|
|
1912
|
+
const oldExt = oldFilename.slice(oldFilename.lastIndexOf("."));
|
|
1913
|
+
const newExt = newFilename.slice(newFilename.lastIndexOf("."));
|
|
1914
|
+
if (oldExt !== newExt) {
|
|
1915
|
+
return res.status(400).json({
|
|
1916
|
+
error: "File extension cannot be changed",
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
// Validar caracteres prohibidos según GCS: < > : " / \ | ? * [ ] # y caracteres de control
|
|
1920
|
+
const invalidCharsList = [
|
|
1921
|
+
"<",
|
|
1922
|
+
">",
|
|
1923
|
+
":",
|
|
1924
|
+
'"',
|
|
1925
|
+
"/",
|
|
1926
|
+
"\\",
|
|
1927
|
+
"|",
|
|
1928
|
+
"?",
|
|
1929
|
+
"*",
|
|
1930
|
+
"[",
|
|
1931
|
+
"]",
|
|
1932
|
+
"#",
|
|
1933
|
+
"\r",
|
|
1934
|
+
"\n",
|
|
1935
|
+
];
|
|
1936
|
+
const hasInvalidChars = invalidCharsList.some(char => newFilename.includes(char));
|
|
1937
|
+
if (hasInvalidChars) {
|
|
1938
|
+
return res.status(400).json({
|
|
1939
|
+
error: "Filename contains invalid characters",
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
// Validar nombres reservados
|
|
1943
|
+
if (newFilename === "." ||
|
|
1944
|
+
newFilename === ".." ||
|
|
1945
|
+
newFilename.startsWith(".well-known/acme-challenge/")) {
|
|
1946
|
+
return res.status(400).json({
|
|
1947
|
+
error: "Reserved filename not allowed",
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
// Validar longitud (máximo 1024 bytes en UTF-8)
|
|
1951
|
+
const newFilenameBytes = buffer_1.Buffer.from(newFilename, "utf8").length;
|
|
1952
|
+
if (newFilenameBytes > 1024) {
|
|
1953
|
+
return res.status(400).json({
|
|
1954
|
+
error: "Filename exceeds maximum size (1024 bytes in UTF-8)",
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
const oldFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldFilename}`;
|
|
1958
|
+
const newFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newFilename}`;
|
|
1959
|
+
const oldFile = bucket.file(oldFilePath);
|
|
1960
|
+
const newFile = bucket.file(newFilePath);
|
|
1961
|
+
// Verificar que el archivo original existe
|
|
1962
|
+
const [oldExists] = await oldFile.exists();
|
|
1963
|
+
if (!oldExists) {
|
|
1964
|
+
return res.status(404).json({ error: "Source file not found" });
|
|
1965
|
+
}
|
|
1966
|
+
// Verificar que el nuevo nombre no existe
|
|
1967
|
+
const [newExists] = await newFile.exists();
|
|
1968
|
+
if (newExists) {
|
|
1969
|
+
return res
|
|
1970
|
+
.status(409)
|
|
1971
|
+
.json({ error: "File with new name already exists" });
|
|
1972
|
+
}
|
|
1973
|
+
// GCS no permite renombrar directamente, usar copy + delete
|
|
1974
|
+
await oldFile.copy(newFile);
|
|
1975
|
+
await oldFile.delete();
|
|
1976
|
+
console.log(`✅ Renamed file: ${oldFilePath} -> ${newFilePath}`);
|
|
1977
|
+
// Si existe un archivo de solución correspondiente, renombrarlo también
|
|
1978
|
+
// Patrón: nombreBase.solution.hide.extension
|
|
1979
|
+
let solutionRenamed = false;
|
|
1980
|
+
const lastDotIndex = oldFilename.lastIndexOf(".");
|
|
1981
|
+
if (lastDotIndex > 0 && lastDotIndex < oldFilename.length - 1) {
|
|
1982
|
+
// Usar la extensión que ya validamos que coincide
|
|
1983
|
+
const extension = oldExt; // Ya validado anteriormente
|
|
1984
|
+
const oldBaseName = oldFilename.slice(0, lastDotIndex);
|
|
1985
|
+
const newBaseName = newFilename.slice(0, newFilename.lastIndexOf("."));
|
|
1986
|
+
const oldSolutionFileName = `${oldBaseName}.solution.hide${extension}`;
|
|
1987
|
+
const newSolutionFileName = `${newBaseName}.solution.hide${extension}`;
|
|
1988
|
+
const oldSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldSolutionFileName}`;
|
|
1989
|
+
const newSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newSolutionFileName}`;
|
|
1990
|
+
const oldSolutionFile = bucket.file(oldSolutionFilePath);
|
|
1991
|
+
const newSolutionFile = bucket.file(newSolutionFilePath);
|
|
1992
|
+
const [solutionExists] = await oldSolutionFile.exists();
|
|
1993
|
+
if (solutionExists) {
|
|
1994
|
+
// Verificar que el nuevo nombre de solución no exista
|
|
1995
|
+
const [newSolutionExists] = await newSolutionFile.exists();
|
|
1996
|
+
if (!newSolutionExists) {
|
|
1997
|
+
await oldSolutionFile.copy(newSolutionFile);
|
|
1998
|
+
await oldSolutionFile.delete();
|
|
1999
|
+
solutionRenamed = true;
|
|
2000
|
+
console.log(`✅ Renamed solution file: ${oldSolutionFilePath} -> ${newSolutionFilePath}`);
|
|
2001
|
+
}
|
|
2002
|
+
else {
|
|
2003
|
+
console.log(`⚠️ Solution file ${newSolutionFileName} already exists, skipping rename`);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
// Caso sin extensión: nombre.solution.hide
|
|
2009
|
+
const oldSolutionFileName = `${oldFilename}.solution.hide`;
|
|
2010
|
+
const newSolutionFileName = `${newFilename}.solution.hide`;
|
|
2011
|
+
const oldSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldSolutionFileName}`;
|
|
2012
|
+
const newSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newSolutionFileName}`;
|
|
2013
|
+
const oldSolutionFile = bucket.file(oldSolutionFilePath);
|
|
2014
|
+
const newSolutionFile = bucket.file(newSolutionFilePath);
|
|
2015
|
+
const [solutionExists] = await oldSolutionFile.exists();
|
|
2016
|
+
if (solutionExists) {
|
|
2017
|
+
const [newSolutionExists] = await newSolutionFile.exists();
|
|
2018
|
+
if (!newSolutionExists) {
|
|
2019
|
+
await oldSolutionFile.copy(newSolutionFile);
|
|
2020
|
+
await oldSolutionFile.delete();
|
|
2021
|
+
solutionRenamed = true;
|
|
2022
|
+
console.log(`✅ Renamed solution file: ${oldSolutionFilePath} -> ${newSolutionFilePath}`);
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
console.log(`⚠️ Solution file ${newSolutionFileName} already exists, skipping rename`);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
return res.json({
|
|
2030
|
+
success: true,
|
|
2031
|
+
message: "File renamed successfully",
|
|
2032
|
+
solutionRenamed,
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
catch (error) {
|
|
2036
|
+
console.error("❌ Error renaming file:", error);
|
|
2037
|
+
return res.status(500).json({
|
|
2038
|
+
error: error.message || "Unable to rename file",
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
1894
2042
|
const YT_REGEX = /(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]{11})/;
|
|
1895
2043
|
app.get("/actions/fetch/:link", async (req, res) => {
|
|
1896
2044
|
var _a, _b, _c;
|
|
@@ -25605,7 +25605,9 @@ function Pre({ detectedLanguage: e, onSwitch: t, onStay: a }) {
|
|
|
25605
25605
|
className:
|
|
25606
25606
|
"flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md font-semibold hover:bg-gray-300 transition-colors",
|
|
25607
25607
|
children: n(
|
|
25608
|
-
`languageDetection.stayIn${
|
|
25608
|
+
`languageDetection.stayIn${
|
|
25609
|
+
r.language.toLowerCase().split("-")[0]
|
|
25610
|
+
}`
|
|
25609
25611
|
),
|
|
25610
25612
|
}),
|
|
25611
25613
|
],
|
|
@@ -25727,8 +25729,7 @@ function Bre() {
|
|
|
25727
25729
|
O = "";
|
|
25728
25730
|
if (
|
|
25729
25731
|
(m.rigoToken &&
|
|
25730
|
-
(
|
|
25731
|
-
(await DU(m.rigoToken))
|
|
25732
|
+
((await DU(m.rigoToken))
|
|
25732
25733
|
? ((F = m.rigoToken), (R = !0), (O = "rigo"))
|
|
25733
25734
|
: s({
|
|
25734
25735
|
...m,
|
|
@@ -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-XZDcEWl9.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-CjddKHB_.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -96,7 +96,7 @@ type TInitialContentGeneratorInputs = {
|
|
|
96
96
|
current_syllabus: string;
|
|
97
97
|
lesson_description: string;
|
|
98
98
|
};
|
|
99
|
-
export declare const initialContentGenerator: (token: string, inputs: TInitialContentGeneratorInputs, webhookUrl?: string) => Promise<any>;
|
|
99
|
+
export declare const initialContentGenerator: (token: string, inputs: TInitialContentGeneratorInputs, webhookUrl?: string, endpointSlug?: string) => Promise<any>;
|
|
100
100
|
type TAddInteractivityInputs = {
|
|
101
101
|
components: string;
|
|
102
102
|
prev_lesson: string;
|
package/lib/utils/rigoActions.js
CHANGED
|
@@ -283,9 +283,9 @@ const getLanguageCodes = async (token, inputs) => {
|
|
|
283
283
|
return response.data;
|
|
284
284
|
};
|
|
285
285
|
exports.getLanguageCodes = getLanguageCodes;
|
|
286
|
-
const initialContentGenerator = async (token, inputs, webhookUrl) => {
|
|
286
|
+
const initialContentGenerator = async (token, inputs, webhookUrl, endpointSlug = "initial-step-content-generator") => {
|
|
287
287
|
try {
|
|
288
|
-
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion
|
|
288
|
+
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/${endpointSlug}/`, {
|
|
289
289
|
inputs,
|
|
290
290
|
include_purpose_objective: false,
|
|
291
291
|
execute_async: !!webhookUrl,
|
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.319",
|
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
|
6
6
|
"contributors": [
|
|
7
7
|
{
|
package/src/commands/serve.ts
CHANGED
|
@@ -330,6 +330,15 @@ async function startInitialContentGeneration(
|
|
|
330
330
|
|
|
331
331
|
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`
|
|
332
332
|
|
|
333
|
+
// Determine if this is the first lesson
|
|
334
|
+
const exerciseIndex = steps.findIndex(
|
|
335
|
+
lesson => lesson.uid === exercise.uid
|
|
336
|
+
)
|
|
337
|
+
const isFirstLesson = exerciseIndex === 0
|
|
338
|
+
const endpointSlug = isFirstLesson ?
|
|
339
|
+
"initial-step-content-generator" :
|
|
340
|
+
"generate-step-initial-content"
|
|
341
|
+
|
|
333
342
|
// Emit notification that initial content generation is starting
|
|
334
343
|
emitToCourse(courseSlug, "course-creation", {
|
|
335
344
|
lesson: exSlug,
|
|
@@ -355,7 +364,8 @@ async function startInitialContentGeneration(
|
|
|
355
364
|
lesson_description:
|
|
356
365
|
JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
|
|
357
366
|
},
|
|
358
|
-
webhookUrl
|
|
367
|
+
webhookUrl,
|
|
368
|
+
endpointSlug
|
|
359
369
|
)
|
|
360
370
|
|
|
361
371
|
console.log("INITIAL CONTENT GENERATOR RES", res)
|
|
@@ -2808,6 +2818,190 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2808
2818
|
}
|
|
2809
2819
|
)
|
|
2810
2820
|
|
|
2821
|
+
app.put(
|
|
2822
|
+
"/courses/:courseSlug/exercises/:exerciseSlug/file/:filename/rename",
|
|
2823
|
+
express.json(),
|
|
2824
|
+
async (req, res) => {
|
|
2825
|
+
console.log(
|
|
2826
|
+
"PUT /courses/:courseSlug/exercises/:exerciseSlug/file/:filename/rename",
|
|
2827
|
+
req.params,
|
|
2828
|
+
req.body
|
|
2829
|
+
)
|
|
2830
|
+
const { courseSlug, exerciseSlug, filename } = req.params
|
|
2831
|
+
const { oldFilename, newFilename } = req.body
|
|
2832
|
+
|
|
2833
|
+
try {
|
|
2834
|
+
// Validaciones
|
|
2835
|
+
if (!oldFilename || !newFilename) {
|
|
2836
|
+
return res.status(400).json({
|
|
2837
|
+
error: "oldFilename and newFilename are required",
|
|
2838
|
+
})
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// Validar que las extensiones coincidan
|
|
2842
|
+
const oldExt = oldFilename.slice(oldFilename.lastIndexOf("."))
|
|
2843
|
+
const newExt = newFilename.slice(newFilename.lastIndexOf("."))
|
|
2844
|
+
if (oldExt !== newExt) {
|
|
2845
|
+
return res.status(400).json({
|
|
2846
|
+
error: "File extension cannot be changed",
|
|
2847
|
+
})
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// Validar caracteres prohibidos según GCS: < > : " / \ | ? * [ ] # y caracteres de control
|
|
2851
|
+
const invalidCharsList = [
|
|
2852
|
+
"<",
|
|
2853
|
+
">",
|
|
2854
|
+
":",
|
|
2855
|
+
'"',
|
|
2856
|
+
"/",
|
|
2857
|
+
"\\",
|
|
2858
|
+
"|",
|
|
2859
|
+
"?",
|
|
2860
|
+
"*",
|
|
2861
|
+
"[",
|
|
2862
|
+
"]",
|
|
2863
|
+
"#",
|
|
2864
|
+
"\r",
|
|
2865
|
+
"\n",
|
|
2866
|
+
]
|
|
2867
|
+
const hasInvalidChars = invalidCharsList.some(char =>
|
|
2868
|
+
newFilename.includes(char)
|
|
2869
|
+
)
|
|
2870
|
+
if (hasInvalidChars) {
|
|
2871
|
+
return res.status(400).json({
|
|
2872
|
+
error: "Filename contains invalid characters",
|
|
2873
|
+
})
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// Validar nombres reservados
|
|
2877
|
+
if (
|
|
2878
|
+
newFilename === "." ||
|
|
2879
|
+
newFilename === ".." ||
|
|
2880
|
+
newFilename.startsWith(".well-known/acme-challenge/")
|
|
2881
|
+
) {
|
|
2882
|
+
return res.status(400).json({
|
|
2883
|
+
error: "Reserved filename not allowed",
|
|
2884
|
+
})
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// Validar longitud (máximo 1024 bytes en UTF-8)
|
|
2888
|
+
const newFilenameBytes = Buffer.from(newFilename, "utf8").length
|
|
2889
|
+
if (newFilenameBytes > 1024) {
|
|
2890
|
+
return res.status(400).json({
|
|
2891
|
+
error: "Filename exceeds maximum size (1024 bytes in UTF-8)",
|
|
2892
|
+
})
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
const oldFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldFilename}`
|
|
2896
|
+
const newFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newFilename}`
|
|
2897
|
+
|
|
2898
|
+
const oldFile = bucket.file(oldFilePath)
|
|
2899
|
+
const newFile = bucket.file(newFilePath)
|
|
2900
|
+
|
|
2901
|
+
// Verificar que el archivo original existe
|
|
2902
|
+
const [oldExists] = await oldFile.exists()
|
|
2903
|
+
if (!oldExists) {
|
|
2904
|
+
return res.status(404).json({ error: "Source file not found" })
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
// Verificar que el nuevo nombre no existe
|
|
2908
|
+
const [newExists] = await newFile.exists()
|
|
2909
|
+
if (newExists) {
|
|
2910
|
+
return res
|
|
2911
|
+
.status(409)
|
|
2912
|
+
.json({ error: "File with new name already exists" })
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// GCS no permite renombrar directamente, usar copy + delete
|
|
2916
|
+
await oldFile.copy(newFile)
|
|
2917
|
+
await oldFile.delete()
|
|
2918
|
+
|
|
2919
|
+
console.log(`✅ Renamed file: ${oldFilePath} -> ${newFilePath}`)
|
|
2920
|
+
|
|
2921
|
+
// Si existe un archivo de solución correspondiente, renombrarlo también
|
|
2922
|
+
// Patrón: nombreBase.solution.hide.extension
|
|
2923
|
+
let solutionRenamed = false
|
|
2924
|
+
const lastDotIndex = oldFilename.lastIndexOf(".")
|
|
2925
|
+
if (lastDotIndex > 0 && lastDotIndex < oldFilename.length - 1) {
|
|
2926
|
+
// Usar la extensión que ya validamos que coincide
|
|
2927
|
+
const extension = oldExt // Ya validado anteriormente
|
|
2928
|
+
const oldBaseName = oldFilename.slice(0, lastDotIndex)
|
|
2929
|
+
const newBaseName = newFilename.slice(
|
|
2930
|
+
0,
|
|
2931
|
+
newFilename.lastIndexOf(".")
|
|
2932
|
+
)
|
|
2933
|
+
|
|
2934
|
+
const oldSolutionFileName = `${oldBaseName}.solution.hide${extension}`
|
|
2935
|
+
const newSolutionFileName = `${newBaseName}.solution.hide${extension}`
|
|
2936
|
+
|
|
2937
|
+
const oldSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldSolutionFileName}`
|
|
2938
|
+
const newSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newSolutionFileName}`
|
|
2939
|
+
|
|
2940
|
+
const oldSolutionFile = bucket.file(oldSolutionFilePath)
|
|
2941
|
+
const newSolutionFile = bucket.file(newSolutionFilePath)
|
|
2942
|
+
|
|
2943
|
+
const [solutionExists] = await oldSolutionFile.exists()
|
|
2944
|
+
|
|
2945
|
+
if (solutionExists) {
|
|
2946
|
+
// Verificar que el nuevo nombre de solución no exista
|
|
2947
|
+
const [newSolutionExists] = await newSolutionFile.exists()
|
|
2948
|
+
if (!newSolutionExists) {
|
|
2949
|
+
await oldSolutionFile.copy(newSolutionFile)
|
|
2950
|
+
await oldSolutionFile.delete()
|
|
2951
|
+
solutionRenamed = true
|
|
2952
|
+
console.log(
|
|
2953
|
+
`✅ Renamed solution file: ${oldSolutionFilePath} -> ${newSolutionFilePath}`
|
|
2954
|
+
)
|
|
2955
|
+
} else {
|
|
2956
|
+
console.log(
|
|
2957
|
+
`⚠️ Solution file ${newSolutionFileName} already exists, skipping rename`
|
|
2958
|
+
)
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
} else {
|
|
2962
|
+
// Caso sin extensión: nombre.solution.hide
|
|
2963
|
+
const oldSolutionFileName = `${oldFilename}.solution.hide`
|
|
2964
|
+
const newSolutionFileName = `${newFilename}.solution.hide`
|
|
2965
|
+
|
|
2966
|
+
const oldSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${oldSolutionFileName}`
|
|
2967
|
+
const newSolutionFilePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${newSolutionFileName}`
|
|
2968
|
+
|
|
2969
|
+
const oldSolutionFile = bucket.file(oldSolutionFilePath)
|
|
2970
|
+
const newSolutionFile = bucket.file(newSolutionFilePath)
|
|
2971
|
+
|
|
2972
|
+
const [solutionExists] = await oldSolutionFile.exists()
|
|
2973
|
+
|
|
2974
|
+
if (solutionExists) {
|
|
2975
|
+
const [newSolutionExists] = await newSolutionFile.exists()
|
|
2976
|
+
if (!newSolutionExists) {
|
|
2977
|
+
await oldSolutionFile.copy(newSolutionFile)
|
|
2978
|
+
await oldSolutionFile.delete()
|
|
2979
|
+
solutionRenamed = true
|
|
2980
|
+
console.log(
|
|
2981
|
+
`✅ Renamed solution file: ${oldSolutionFilePath} -> ${newSolutionFilePath}`
|
|
2982
|
+
)
|
|
2983
|
+
} else {
|
|
2984
|
+
console.log(
|
|
2985
|
+
`⚠️ Solution file ${newSolutionFileName} already exists, skipping rename`
|
|
2986
|
+
)
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
return res.json({
|
|
2992
|
+
success: true,
|
|
2993
|
+
message: "File renamed successfully",
|
|
2994
|
+
solutionRenamed,
|
|
2995
|
+
})
|
|
2996
|
+
} catch (error) {
|
|
2997
|
+
console.error("❌ Error renaming file:", error)
|
|
2998
|
+
return res.status(500).json({
|
|
2999
|
+
error: (error as Error).message || "Unable to rename file",
|
|
3000
|
+
})
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
)
|
|
3004
|
+
|
|
2811
3005
|
const YT_REGEX = /(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]{11})/
|
|
2812
3006
|
|
|
2813
3007
|
app.get("/actions/fetch/:link", async (req, res) => {
|
package/src/creator/src/App.tsx
CHANGED
|
@@ -205,7 +205,6 @@ function App() {
|
|
|
205
205
|
let tokenToUse = ""
|
|
206
206
|
let tokenType = ""
|
|
207
207
|
if (auth.rigoToken) {
|
|
208
|
-
console.log("auth.rigoToken", auth.rigoToken)
|
|
209
208
|
const isRigoTokenValid = await isValidRigoToken(auth.rigoToken)
|
|
210
209
|
if (isRigoTokenValid) {
|
|
211
210
|
tokenToUse = auth.rigoToken
|
|
@@ -37,7 +37,7 @@ export default function LanguageDetectionModal({
|
|
|
37
37
|
onClick={onStay}
|
|
38
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
39
|
>
|
|
40
|
-
{t(`languageDetection.stayIn${i18n.language.toLowerCase()}`)}
|
|
40
|
+
{t(`languageDetection.stayIn${i18n.language.toLowerCase().split("-")[0]}`)}
|
|
41
41
|
</button>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
@@ -25605,7 +25605,9 @@ function Pre({ detectedLanguage: e, onSwitch: t, onStay: a }) {
|
|
|
25605
25605
|
className:
|
|
25606
25606
|
"flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md font-semibold hover:bg-gray-300 transition-colors",
|
|
25607
25607
|
children: n(
|
|
25608
|
-
`languageDetection.stayIn${
|
|
25608
|
+
`languageDetection.stayIn${
|
|
25609
|
+
r.language.toLowerCase().split("-")[0]
|
|
25610
|
+
}`
|
|
25609
25611
|
),
|
|
25610
25612
|
}),
|
|
25611
25613
|
],
|
|
@@ -25727,8 +25729,7 @@ function Bre() {
|
|
|
25727
25729
|
O = "";
|
|
25728
25730
|
if (
|
|
25729
25731
|
(m.rigoToken &&
|
|
25730
|
-
(
|
|
25731
|
-
(await DU(m.rigoToken))
|
|
25732
|
+
((await DU(m.rigoToken))
|
|
25732
25733
|
? ((F = m.rigoToken), (R = !0), (O = "rigo"))
|
|
25733
25734
|
: s({
|
|
25734
25735
|
...m,
|
|
@@ -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-XZDcEWl9.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-CjddKHB_.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|