@learnpack/learnpack 5.0.67 → 5.0.69
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/{src/creatorDist/assets/index-tZYXMzIW.js → lib/creatorDist/assets/index-C7bLE5wU.js} +11189 -11028
- package/lib/creatorDist/assets/{index-t6ma_gVm.css → index-C_Rp91QE.css} +136 -5
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.js +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/src/assets/svgs.tsx +40 -0
- package/src/creator/src/components/ConsumablesManager.tsx +21 -0
- package/src/creator/src/components/syllabus/ContentIndex.tsx +167 -0
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +252 -0
- package/src/creator/src/main.tsx +1 -1
- package/src/creator/src/utils/lib.ts +80 -0
- package/src/creator/src/utils/store.ts +20 -1
- package/{lib/creatorDist/assets/index-tZYXMzIW.js → src/creatorDist/assets/index-C7bLE5wU.js} +11189 -11028
- package/src/creatorDist/assets/{index-t6ma_gVm.css → index-C_Rp91QE.css} +136 -5
- package/src/creatorDist/index.html +2 -2
- package/src/utils/api.ts +2 -2
- package/src/creator/src/components/SyllabusEditor.tsx +0 -281
@@ -17,6 +17,15 @@
|
|
17
17
|
--tw-space-y-reverse: 0;
|
18
18
|
--tw-space-x-reverse: 0;
|
19
19
|
--tw-border-style: solid;
|
20
|
+
--tw-gradient-position: initial;
|
21
|
+
--tw-gradient-from: #0000;
|
22
|
+
--tw-gradient-via: #0000;
|
23
|
+
--tw-gradient-to: #0000;
|
24
|
+
--tw-gradient-stops: initial;
|
25
|
+
--tw-gradient-via-stops: initial;
|
26
|
+
--tw-gradient-from-position: 0%;
|
27
|
+
--tw-gradient-via-position: 50%;
|
28
|
+
--tw-gradient-to-position: 100%;
|
20
29
|
--tw-leading: initial;
|
21
30
|
--tw-font-weight: initial;
|
22
31
|
--tw-shadow: 0 0 #0000;
|
@@ -346,6 +355,12 @@
|
|
346
355
|
}
|
347
356
|
@layer components;
|
348
357
|
@layer utilities {
|
358
|
+
.pointer-events-auto {
|
359
|
+
pointer-events: auto;
|
360
|
+
}
|
361
|
+
.pointer-events-none {
|
362
|
+
pointer-events: none;
|
363
|
+
}
|
349
364
|
.absolute {
|
350
365
|
position: absolute;
|
351
366
|
}
|
@@ -364,15 +379,58 @@
|
|
364
379
|
.right-2 {
|
365
380
|
right: calc(var(--spacing) * 2);
|
366
381
|
}
|
382
|
+
.bottom-0 {
|
383
|
+
bottom: calc(var(--spacing) * 0);
|
384
|
+
}
|
367
385
|
.bottom-2 {
|
368
386
|
bottom: calc(var(--spacing) * 2);
|
369
387
|
}
|
388
|
+
.bottom-3 {
|
389
|
+
bottom: calc(var(--spacing) * 3);
|
390
|
+
}
|
370
391
|
.bottom-4 {
|
371
392
|
bottom: calc(var(--spacing) * 4);
|
372
393
|
}
|
394
|
+
.left-0 {
|
395
|
+
left: calc(var(--spacing) * 0);
|
396
|
+
}
|
373
397
|
.left-1\/2 {
|
374
398
|
left: 50%;
|
375
399
|
}
|
400
|
+
.z-10 {
|
401
|
+
z-index: 10;
|
402
|
+
}
|
403
|
+
.z-20 {
|
404
|
+
z-index: 20;
|
405
|
+
}
|
406
|
+
.container {
|
407
|
+
width: 100%;
|
408
|
+
}
|
409
|
+
@media (min-width: 40rem) {
|
410
|
+
.container {
|
411
|
+
max-width: 40rem;
|
412
|
+
}
|
413
|
+
}
|
414
|
+
@media (min-width: 48rem) {
|
415
|
+
.container {
|
416
|
+
max-width: 48rem;
|
417
|
+
}
|
418
|
+
}
|
419
|
+
@media (min-width: 64rem) {
|
420
|
+
.container {
|
421
|
+
max-width: 64rem;
|
422
|
+
}
|
423
|
+
}
|
424
|
+
@media (min-width: 80rem) {
|
425
|
+
.container {
|
426
|
+
max-width: 80rem;
|
427
|
+
}
|
428
|
+
}
|
429
|
+
@media (min-width: 96rem) {
|
430
|
+
.container {
|
431
|
+
max-width: 96rem;
|
432
|
+
}
|
433
|
+
}
|
376
434
|
.m-0 {
|
377
435
|
margin: calc(var(--spacing) * 0);
|
378
436
|
}
|
@@ -436,6 +494,9 @@
|
|
436
494
|
.h-6 {
|
437
495
|
height: calc(var(--spacing) * 6);
|
438
496
|
}
|
497
|
+
.h-20 {
|
498
|
+
height: calc(var(--spacing) * 20);
|
499
|
+
}
|
439
500
|
.h-24 {
|
440
501
|
height: calc(var(--spacing) * 24);
|
441
502
|
}
|
@@ -445,8 +506,8 @@
|
|
445
506
|
.h-screen {
|
446
507
|
height: 100vh;
|
447
508
|
}
|
448
|
-
.max-h-\[
|
449
|
-
max-height:
|
509
|
+
.max-h-\[70vh\] {
|
510
|
+
max-height: 70vh;
|
450
511
|
}
|
451
512
|
.max-h-\[300px\] {
|
452
513
|
max-height: 300px;
|
@@ -660,15 +721,34 @@
|
|
660
721
|
.bg-yellow-50 {
|
661
722
|
background-color: var(--color-yellow-50);
|
662
723
|
}
|
724
|
+
.bg-gradient-to-t {
|
725
|
+
--tw-gradient-position: to top in oklab;
|
726
|
+
background-image: linear-gradient(var(--tw-gradient-stops));
|
727
|
+
}
|
728
|
+
.from-white {
|
729
|
+
--tw-gradient-from: var(--color-white);
|
730
|
+
--tw-gradient-stops: var(
|
731
|
+
--tw-gradient-via-stops,
|
732
|
+
var(--tw-gradient-position),
|
733
|
+
var(--tw-gradient-from) var(--tw-gradient-from-position),
|
734
|
+
var(--tw-gradient-to) var(--tw-gradient-to-position)
|
735
|
+
);
|
736
|
+
}
|
737
|
+
.to-transparent {
|
738
|
+
--tw-gradient-to: transparent;
|
739
|
+
--tw-gradient-stops: var(
|
740
|
+
--tw-gradient-via-stops,
|
741
|
+
var(--tw-gradient-position),
|
742
|
+
var(--tw-gradient-from) var(--tw-gradient-from-position),
|
743
|
+
var(--tw-gradient-to) var(--tw-gradient-to-position)
|
744
|
+
);
|
745
|
+
}
|
663
746
|
.p-2 {
|
664
747
|
padding: calc(var(--spacing) * 2);
|
665
748
|
}
|
666
749
|
.p-3 {
|
667
750
|
padding: calc(var(--spacing) * 3);
|
668
751
|
}
|
669
|
-
.p-4 {
|
670
|
-
padding: calc(var(--spacing) * 4);
|
671
|
-
}
|
672
752
|
.p-6 {
|
673
753
|
padding: calc(var(--spacing) * 6);
|
674
754
|
}
|
@@ -785,6 +865,12 @@
|
|
785
865
|
.opacity-30 {
|
786
866
|
opacity: 0.3;
|
787
867
|
}
|
868
|
+
.shadow {
|
869
|
+
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a),
|
870
|
+
0 1px 2px -1px var(--tw-shadow-color, #0000001a);
|
871
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
|
872
|
+
var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
873
|
+
}
|
788
874
|
.shadow-md {
|
789
875
|
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, #0000001a),
|
790
876
|
0 2px 4px -2px var(--tw-shadow-color, #0000001a);
|
@@ -806,6 +892,9 @@
|
|
806
892
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
807
893
|
}
|
808
894
|
@media (hover: hover) {
|
895
|
+
.hover\:bg-blue-50:hover {
|
896
|
+
background-color: var(--color-blue-50);
|
897
|
+
}
|
809
898
|
.hover\:bg-blue-200:hover {
|
810
899
|
background-color: var(--color-blue-200);
|
811
900
|
}
|
@@ -998,6 +1087,48 @@ h1 {
|
|
998
1087
|
inherits: false;
|
999
1088
|
initial-value: solid;
|
1000
1089
|
}
|
1090
|
+
@property --tw-gradient-position {
|
1091
|
+
syntax: "*";
|
1092
|
+
inherits: false;
|
1093
|
+
}
|
1094
|
+
@property --tw-gradient-from {
|
1095
|
+
syntax: "<color>";
|
1096
|
+
inherits: false;
|
1097
|
+
initial-value: #0000;
|
1098
|
+
}
|
1099
|
+
@property --tw-gradient-via {
|
1100
|
+
syntax: "<color>";
|
1101
|
+
inherits: false;
|
1102
|
+
initial-value: #0000;
|
1103
|
+
}
|
1104
|
+
@property --tw-gradient-to {
|
1105
|
+
syntax: "<color>";
|
1106
|
+
inherits: false;
|
1107
|
+
initial-value: #0000;
|
1108
|
+
}
|
1109
|
+
@property --tw-gradient-stops {
|
1110
|
+
syntax: "*";
|
1111
|
+
inherits: false;
|
1112
|
+
}
|
1113
|
+
@property --tw-gradient-via-stops {
|
1114
|
+
syntax: "*";
|
1115
|
+
inherits: false;
|
1116
|
+
}
|
1117
|
+
@property --tw-gradient-from-position {
|
1118
|
+
syntax: "<length-percentage>";
|
1119
|
+
inherits: false;
|
1120
|
+
initial-value: 0%;
|
1121
|
+
}
|
1122
|
+
@property --tw-gradient-via-position {
|
1123
|
+
syntax: "<length-percentage>";
|
1124
|
+
inherits: false;
|
1125
|
+
initial-value: 50%;
|
1126
|
+
}
|
1127
|
+
@property --tw-gradient-to-position {
|
1128
|
+
syntax: "<length-percentage>";
|
1129
|
+
inherits: false;
|
1130
|
+
initial-value: 100%;
|
1131
|
+
}
|
1001
1132
|
@property --tw-leading {
|
1002
1133
|
syntax: "*";
|
1003
1134
|
inherits: false;
|
@@ -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-C7bLE5wU.js"></script>
|
14
|
+
<link rel="stylesheet" crossorigin href="/creator/assets/index-C_Rp91QE.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
17
17
|
<div id="root"></div>
|
package/lib/utils/api.js
CHANGED
@@ -274,11 +274,11 @@ const getConsumable = async (token, consumableSlug = "ai-generation") => {
|
|
274
274
|
exports.getConsumable = getConsumable;
|
275
275
|
const with_crud_asset_roles = new Set([
|
276
276
|
"content_writer",
|
277
|
-
"growth_manager",
|
278
277
|
"syllabus_coordinator",
|
279
278
|
"country_manager",
|
280
|
-
"community_manager",
|
281
279
|
"carrer_support_head",
|
280
|
+
"admin",
|
281
|
+
"student",
|
282
282
|
]);
|
283
283
|
const listUserAcademies = async (breathecodeToken) => {
|
284
284
|
const url = "https://breathecode.herokuapp.com/v1/auth/user/me";
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.69","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.69",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
@@ -223,4 +223,44 @@ export const SVGS = {
|
|
223
223
|
/>
|
224
224
|
</svg>
|
225
225
|
),
|
226
|
+
downArrow: (
|
227
|
+
<svg
|
228
|
+
width="7"
|
229
|
+
height="6"
|
230
|
+
viewBox="0 0 7 6"
|
231
|
+
fill="none"
|
232
|
+
xmlns="http://www.w3.org/2000/svg"
|
233
|
+
>
|
234
|
+
<path
|
235
|
+
fillRule="evenodd"
|
236
|
+
clipRule="evenodd"
|
237
|
+
d="M0.100496 1.0705C0.22866 0.950272 0.430019 0.956708 0.550243 1.08487L3.5 4.2294L6.44976 1.08487C6.56998 0.956708 6.77134 0.950272 6.8995 1.0705C7.02767 1.19072 7.0341 1.39208 6.91388 1.52024L3.73206 4.91216C3.67191 4.97628 3.58792 5.01266 3.5 5.01266C3.41208 5.01266 3.32809 4.97628 3.26794 4.91216L0.086122 1.52024C-0.0341029 1.39208 -0.0276674 1.19072 0.100496 1.0705Z"
|
238
|
+
fill="#0084FF"
|
239
|
+
/>
|
240
|
+
</svg>
|
241
|
+
),
|
242
|
+
bottom: (
|
243
|
+
<svg
|
244
|
+
width="12"
|
245
|
+
height="12"
|
246
|
+
viewBox="0 0 24 24"
|
247
|
+
fill="none"
|
248
|
+
xmlns="http://www.w3.org/2000/svg"
|
249
|
+
>
|
250
|
+
<path
|
251
|
+
d="M11.9297 2V22"
|
252
|
+
stroke="#0084FF"
|
253
|
+
strokeWidth="1"
|
254
|
+
strokeLinecap="round"
|
255
|
+
strokeLinejoin="round"
|
256
|
+
/>
|
257
|
+
<path
|
258
|
+
d="M19 16L14 21.1599C13.7437 21.4336 13.434 21.6519 13.0899 21.801C12.7459 21.9502 12.375 22.0271 12 22.0271C11.625 22.0271 11.2541 21.9502 10.9101 21.801C10.566 21.6519 10.2563 21.4336 10 21.1599L5 16"
|
259
|
+
stroke="#0084FF"
|
260
|
+
strokeWidth="1"
|
261
|
+
strokeLinecap="round"
|
262
|
+
strokeLinejoin="round"
|
263
|
+
/>
|
264
|
+
</svg>
|
265
|
+
),
|
226
266
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { useEffect } from "react"
|
2
|
+
import { getConsumables, parseConsumables } from "../utils/lib"
|
3
|
+
import useStore from "../utils/store"
|
4
|
+
|
5
|
+
export const ConsumablesManager = () => {
|
6
|
+
const auth = useStore((state) => state.auth)
|
7
|
+
const setConsumables = useStore((state) => state.setConsumables)
|
8
|
+
|
9
|
+
useEffect(() => {
|
10
|
+
if (auth.bcToken) {
|
11
|
+
fetchConsumables()
|
12
|
+
}
|
13
|
+
}, [auth])
|
14
|
+
|
15
|
+
const fetchConsumables = async () => {
|
16
|
+
const consumables = await getConsumables(auth.bcToken)
|
17
|
+
setConsumables(parseConsumables(consumables.voids))
|
18
|
+
}
|
19
|
+
|
20
|
+
return <></>
|
21
|
+
}
|
@@ -0,0 +1,167 @@
|
|
1
|
+
import { useEffect, useState, useRef } from "react"
|
2
|
+
import useStore from "../../utils/store"
|
3
|
+
import { Lesson, LessonItem } from "../LessonItem"
|
4
|
+
import { SVGS } from "../../assets/svgs"
|
5
|
+
|
6
|
+
export const GenerateButton = ({
|
7
|
+
handleSubmit,
|
8
|
+
}: {
|
9
|
+
handleSubmit: () => void
|
10
|
+
}) => {
|
11
|
+
return (
|
12
|
+
<div className="flex justify-end mt-6">
|
13
|
+
<button
|
14
|
+
onClick={async () => {
|
15
|
+
handleSubmit()
|
16
|
+
}}
|
17
|
+
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
|
18
|
+
>
|
19
|
+
<span>I'm ready. Create the course for me!</span>
|
20
|
+
{SVGS.rigoSoftBlue}
|
21
|
+
</button>
|
22
|
+
</div>
|
23
|
+
)
|
24
|
+
}
|
25
|
+
export const ContentIndex = ({
|
26
|
+
prevLessons,
|
27
|
+
handleSubmit,
|
28
|
+
}: {
|
29
|
+
prevLessons?: Lesson[]
|
30
|
+
handleSubmit: () => void
|
31
|
+
}) => {
|
32
|
+
const syllabus = useStore((state) => state.syllabus)
|
33
|
+
const setSyllabus = useStore((state) => state.setSyllabus)
|
34
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
35
|
+
const [showScrollHint, setShowScrollHint] = useState(false)
|
36
|
+
|
37
|
+
const handleRemove = (id: string) => {
|
38
|
+
setSyllabus({
|
39
|
+
...syllabus,
|
40
|
+
lessons: syllabus.lessons.filter((lesson) => lesson.id !== id),
|
41
|
+
})
|
42
|
+
}
|
43
|
+
|
44
|
+
const handleChange = (id: string, newTitle: string) => {
|
45
|
+
setSyllabus({
|
46
|
+
...syllabus,
|
47
|
+
lessons: syllabus.lessons.map((lesson) =>
|
48
|
+
lesson.id === id ? { ...lesson, title: newTitle } : lesson
|
49
|
+
),
|
50
|
+
})
|
51
|
+
}
|
52
|
+
|
53
|
+
const addLessonAfter = (index: number, id: string) => {
|
54
|
+
const newLesson: Lesson = {
|
55
|
+
id: (parseFloat(id) + 0.1).toString(),
|
56
|
+
title: "Hello World",
|
57
|
+
type: "READ",
|
58
|
+
duration: 2,
|
59
|
+
description: "Hello World",
|
60
|
+
}
|
61
|
+
const updated = [...syllabus.lessons]
|
62
|
+
updated.splice(index + 1, 0, newLesson)
|
63
|
+
|
64
|
+
setSyllabus({
|
65
|
+
lessons: updated,
|
66
|
+
})
|
67
|
+
}
|
68
|
+
|
69
|
+
useEffect(() => {
|
70
|
+
const container = containerRef.current
|
71
|
+
|
72
|
+
const checkScroll = () => {
|
73
|
+
if (container) {
|
74
|
+
const nearBottom =
|
75
|
+
container.scrollHeight >
|
76
|
+
container.clientHeight + container.scrollTop + 100
|
77
|
+
|
78
|
+
setShowScrollHint(nearBottom)
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
checkScroll()
|
83
|
+
container?.addEventListener("scroll", checkScroll)
|
84
|
+
return () => container?.removeEventListener("scroll", checkScroll)
|
85
|
+
}, [syllabus.lessons])
|
86
|
+
|
87
|
+
const scrollToBottom = (target: "bottom" | "continue") => {
|
88
|
+
if (target === "continue") {
|
89
|
+
const container = containerRef.current
|
90
|
+
if (container) {
|
91
|
+
const scrollStep = container.clientHeight * 0.8
|
92
|
+
container.scrollBy({ top: scrollStep, behavior: "smooth" })
|
93
|
+
}
|
94
|
+
} else {
|
95
|
+
const container = containerRef.current
|
96
|
+
if (container) {
|
97
|
+
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" })
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
return (
|
103
|
+
<div className="relative">
|
104
|
+
<div
|
105
|
+
ref={containerRef}
|
106
|
+
className=" space-y-3 overflow-y-auto max-h-[70vh] pr-2 scrollbar-hide relative"
|
107
|
+
>
|
108
|
+
{syllabus.lessons.map((lesson, index) => (
|
109
|
+
<div key={lesson.id}>
|
110
|
+
<LessonItem
|
111
|
+
key={lesson.id}
|
112
|
+
index={lesson.id}
|
113
|
+
lesson={lesson}
|
114
|
+
onChange={handleChange}
|
115
|
+
onRemove={handleRemove}
|
116
|
+
isNew={Boolean(
|
117
|
+
prevLessons &&
|
118
|
+
prevLessons.length > 0 &&
|
119
|
+
!prevLessons.some(
|
120
|
+
(l) =>
|
121
|
+
l.id === lesson.id &&
|
122
|
+
l.title === lesson.title &&
|
123
|
+
l.type === lesson.type
|
124
|
+
)
|
125
|
+
)}
|
126
|
+
/>
|
127
|
+
<div className="relative h-6">
|
128
|
+
<div className="absolute left-1/2 -translate-x-1/2 -top-3">
|
129
|
+
<button
|
130
|
+
onClick={() => addLessonAfter(index, lesson.id)}
|
131
|
+
className="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded hover:bg-blue-200 shadow-sm text-sm font-semibold cursor-pointer"
|
132
|
+
>
|
133
|
+
+
|
134
|
+
</button>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
</div>
|
138
|
+
))}
|
139
|
+
<GenerateButton handleSubmit={handleSubmit} />
|
140
|
+
</div>
|
141
|
+
|
142
|
+
{showScrollHint && (
|
143
|
+
<div className="pointer-events-none relative">
|
144
|
+
<div className="absolute bottom-0 left-0 w-full h-20 bg-gradient-to-t from-white to-transparent z-10" />
|
145
|
+
<div className="absolute bottom-3 left-0 w-full flex justify-center z-20">
|
146
|
+
<button
|
147
|
+
style={{ color: "#0084FF" }}
|
148
|
+
onClick={() => scrollToBottom("continue")}
|
149
|
+
className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
|
150
|
+
>
|
151
|
+
Continue scrolling
|
152
|
+
{SVGS.downArrow}
|
153
|
+
</button>
|
154
|
+
<button
|
155
|
+
style={{ color: "#0084FF" }}
|
156
|
+
onClick={() => scrollToBottom("bottom")}
|
157
|
+
className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
|
158
|
+
>
|
159
|
+
Scroll to bottom
|
160
|
+
{SVGS.bottom}
|
161
|
+
</button>
|
162
|
+
</div>
|
163
|
+
</div>
|
164
|
+
)}
|
165
|
+
</div>
|
166
|
+
)
|
167
|
+
}
|